<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[techwasti]]></title><description><![CDATA[TechWasti is a community where we are sharing thoughts, concepts, ideas, and codes.]]></description><link>https://techwasti.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1644042257872/5JAnycbcS.png</url><title>techwasti</title><link>https://techwasti.com</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 22:29:08 GMT</lastBuildDate><atom:link href="https://techwasti.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Adopting an MCP-First API Design for the Agentic Era | Beyond REST.]]></title><description><![CDATA[Move beyond traditional RESTful thinking. Learn how to design APIs specifically for MCP (Model Context Protocol) servers. This guide covers the shift in mindset, a practical OpenAPI 3.1 example, and a Spring Boot implementation to make your services ...]]></description><link>https://techwasti.com/adopting-an-mcp-first-api-design-for-the-agentic-era-beyond-rest</link><guid isPermaLink="true">https://techwasti.com/adopting-an-mcp-first-api-design-for-the-agentic-era-beyond-rest</guid><category><![CDATA[mcp]]></category><category><![CDATA[genai]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[agents]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[spring ai]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Tue, 17 Feb 2026 16:31:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1771345341450/e1896bc0-45e8-4cda-8e90-e8ad7a215f2d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Move beyond traditional RESTful thinking. Learn how to design APIs specifically for MCP (Model Context Protocol) servers. This guide covers the shift in mindset, a practical OpenAPI 3.1 example, and a Spring Boot implementation to make your services AI-native.</p>
<h1 id="heading-introduction">Introduction:</h1>
<p>For the last decade, our mantra as architects has been "API-first." We designed beautiful, resource-oriented RESTful interfaces, meticulously documented with OpenAPI, to decouple frontends from backends and enable a rich ecosystem of integrations. We built our digital empires on HTTP verbs and status codes.</p>
<p>But a new client has entered the building. It’s not a mobile app or a single-page application. It’s an AI Agent.</p>
<p>This agent doesn't navigate hypermedia links or care about the fine print of your <code>application/json</code> schema in the same way a human developer does. It needs <strong>context</strong> and <strong>capability</strong>. It needs to know: "What can you do for me, and how do I ask you to do it?"</p>
<p>This is where the <strong>Model Context Protocol (MCP)</strong> changes the game. MCP provides a standardized way to expose tools, resources, and prompts to AI models. If you want your enterprise systems to be "AI-native," you must shift from "REST-first" to <strong>"MCP-first" API design.</strong></p>
<h3 id="heading-the-shift-in-mindset-from-resources-to-actions">The Shift in Mindset: From Resources to Actions</h3>
<p>Traditional REST is about managing <em>state</em> (GET, POST, PUT, DELETE on <code>/users</code> or <code>/orders</code>). MCP is about enabling <em>action</em>. An agent doesn't want to just <em>read</em> a user; it wants to "Send a notification to user X" or "Calculate the risk for customer Y."</p>
<p>Therefore, an MCP-first design philosophy treats your APIs as a collection of <strong>tools</strong>. While these tools may be backed by REST endpoints, their public face to the agent ecosystem is a list of declarative functions.</p>
<p><strong>The Golden Rule:</strong> <em>Design the tool first, then implement the API endpoint that supports it.</em></p>
<h3 id="heading-what-is-an-mcp-tool">What is an MCP Tool?</h3>
<p>From an API perspective, an MCP server exposes a tool, which is defined by:</p>
<ol>
<li><p><strong>Name:</strong> A unique identifier for the function (e.g., <code>get_weather</code>).</p>
</li>
<li><p><strong>Description:</strong> A natural language explanation of what the tool does. This is crucial for the LLM to decide when to use it.</p>
</li>
<li><p><strong>Input Schema:</strong> A JSON Schema describing the parameters the tool accepts.</p>
</li>
</ol>
<h3 id="heading-practical-example-the-weather-service-a-classic-for-a-reason">Practical Example: The Weather Service (A Classic for a Reason)</h3>
<p>Let’s say we need to expose an internal weather service to an AI agent so it can answer, "Do I need an umbrella in London tomorrow?"</p>
<h4 id="heading-the-mcp-first-thought-process">The MCP-First Thought Process</h4>
<ol>
<li><p><strong>Identify the Tool:</strong> The agent needs a tool called <code>get_forecast</code>.</p>
</li>
<li><p><strong>Describe it:</strong> "Retrieves the weather forecast for a specific location for a given number of days."</p>
</li>
<li><p><strong>Define the Input:</strong></p>
<ul>
<li><p><code>location</code> (string, required): The city name or coordinates.</p>
</li>
<li><p><code>days</code> (integer, optional): Number of days for the forecast (default: 1).</p>
</li>
</ul>
</li>
</ol>
<p>Now that we know the tool, we design the backing API.</p>
<h4 id="heading-1-the-backing-api-openapi-31-amp-spring-boot">1. The Backing API (OpenAPI 3.1 &amp; Spring Boot)</h4>
<p>The backing API is what your backend team builds. It might be REST, gRPC, or GraphQL. For our example, let's design a simple REST controller in Spring Boot.</p>
<p><strong>OpenAPI 3.1 Specification Snippet (</strong><code>weather-service.yaml</code>)</p>
<pre><code class="lang-yaml"><span class="hljs-attr">openapi:</span> <span class="hljs-number">3.1</span><span class="hljs-number">.0</span>
<span class="hljs-attr">info:</span>
  <span class="hljs-attr">title:</span> <span class="hljs-string">Internal</span> <span class="hljs-string">Weather</span> <span class="hljs-string">API</span>
  <span class="hljs-attr">version:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span>
<span class="hljs-attr">servers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">url:</span> <span class="hljs-string">https://api.internal.company.com/v1</span>
<span class="hljs-attr">paths:</span>
  <span class="hljs-string">/weather/forecast:</span>
    <span class="hljs-attr">get:</span>
      <span class="hljs-attr">summary:</span> <span class="hljs-string">Get</span> <span class="hljs-string">weather</span> <span class="hljs-string">forecast</span>
      <span class="hljs-attr">operationId:</span> <span class="hljs-string">getForecast</span>
      <span class="hljs-attr">parameters:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">location</span>
          <span class="hljs-attr">in:</span> <span class="hljs-string">query</span>
          <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">schema:</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">"City name (e.g., London) or lat,long"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">days</span>
          <span class="hljs-attr">in:</span> <span class="hljs-string">query</span>
          <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
          <span class="hljs-attr">schema:</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">integer</span>
            <span class="hljs-attr">default:</span> <span class="hljs-number">1</span>
            <span class="hljs-attr">minimum:</span> <span class="hljs-number">1</span>
            <span class="hljs-attr">maximum:</span> <span class="hljs-number">7</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">"Number of forecast days"</span>
      <span class="hljs-attr">responses:</span>
        <span class="hljs-attr">'200':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Successful</span> <span class="hljs-string">response</span>
          <span class="hljs-attr">content:</span>
            <span class="hljs-attr">application/json:</span>
              <span class="hljs-attr">schema:</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">object</span>
                <span class="hljs-attr">properties:</span>
                  <span class="hljs-attr">location:</span>
                    <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
                  <span class="hljs-attr">forecast:</span>
                    <span class="hljs-attr">type:</span> <span class="hljs-string">array</span>
                    <span class="hljs-attr">items:</span>
                      <span class="hljs-attr">type:</span> <span class="hljs-string">object</span>
                      <span class="hljs-attr">properties:</span>
                        <span class="hljs-attr">date:</span>
                          <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
                          <span class="hljs-attr">format:</span> <span class="hljs-string">date</span>
                        <span class="hljs-attr">condition:</span>
                          <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
                        <span class="hljs-attr">high:</span>
                          <span class="hljs-attr">type:</span> <span class="hljs-string">number</span>
                        <span class="hljs-attr">low:</span>
                          <span class="hljs-attr">type:</span> <span class="hljs-string">number</span>
</code></pre>
<p><strong>Spring Boot Controller Implementation</strong></p>
<p>This is the code that fulfills the OpenAPI contract.</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping("/api/v1/weather")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WeatherController</span> </span>{

    <span class="hljs-meta">@GetMapping("/forecast")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;ForecastResponse&gt; <span class="hljs-title">getForecast</span><span class="hljs-params">(
            <span class="hljs-meta">@RequestParam</span> String location,
            <span class="hljs-meta">@RequestParam(defaultValue = "1")</span> <span class="hljs-meta">@Min(1)</span> <span class="hljs-meta">@Max(7)</span> <span class="hljs-keyword">int</span> days)</span> </span>{

        <span class="hljs-comment">// In a real app, you'd call a weather service here</span>
        ForecastResponse response = <span class="hljs-keyword">new</span> ForecastResponse(location, days);
        <span class="hljs-keyword">return</span> ResponseEntity.ok(response);
    }

    <span class="hljs-comment">// Inner class for response structure</span>
    <span class="hljs-function">record <span class="hljs-title">ForecastResponse</span><span class="hljs-params">(String location, List&lt;DailyForecast&gt; forecast)</span> </span>{
        ForecastResponse(String location, <span class="hljs-keyword">int</span> days) {
            <span class="hljs-keyword">this</span>(location, generateMockForecast(days));
        }
        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> List&lt;DailyForecast&gt; <span class="hljs-title">generateMockForecast</span><span class="hljs-params">(<span class="hljs-keyword">int</span> days)</span> </span>{
            <span class="hljs-comment">// Mock implementation</span>
            <span class="hljs-keyword">return</span> IntStream.range(<span class="hljs-number">0</span>, days)
                    .mapToObj(i -&gt; <span class="hljs-keyword">new</span> DailyForecast(
                            LocalDate.now().plusDays(i),
                            <span class="hljs-string">"Sunny"</span>,
                            <span class="hljs-number">22.5</span> + i,
                            <span class="hljs-number">13.0</span> + i))
                    .toList();
        }
    }

    <span class="hljs-function">record <span class="hljs-title">DailyForecast</span><span class="hljs-params">(LocalDate date, String condition, Double high, Double low)</span> </span>{}
}
</code></pre>
<p>This is a standard, well-designed REST endpoint. It's ready for mobile apps and web UIs. But it's not yet ready for an AI Agent.</p>
<h4 id="heading-2-the-mcp-server-wrapper-the-adapter">2. The MCP Server Wrapper (The "Adapter")</h4>
<p>This is the critical piece of the MCP-first architecture. We build a lightweight MCP server that acts as an adapter. Its job is to expose our <code>getForecast</code> REST endpoint as an MCP Tool.</p>
<p>Here’s how you might implement that adapter using a Spring Boot MCP abstraction (building on the new Spring AI MCP modules).</p>
<p><strong>MCP Server Tool Definition (Spring Boot).</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.ai.tool.annotation.Tool;
<span class="hljs-keyword">import</span> org.springframework.ai.tool.annotation.ToolParam;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Component;
<span class="hljs-keyword">import</span> org.springframework.web.client.RestClient;
<span class="hljs-keyword">import</span> org.springframework.web.util.UriComponentsBuilder;

<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WeatherTools</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> RestClient restClient;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">WeatherTools</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">this</span>.restClient = RestClient.builder()
                .baseUrl(<span class="hljs-string">"https://api.internal.company.com/v1"</span>)
                .build();
    }

    <span class="hljs-meta">@Tool(description = "Retrieves the weather forecast for a specific location for a given number of days.")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getForecast</span><span class="hljs-params">(
            <span class="hljs-meta">@ToolParam(description = "The city name (e.g., London) or latitude,longitude")</span> String location,
            <span class="hljs-meta">@ToolParam(description = "Number of forecast days (1-7, default is 1)")</span> Integer days)</span> </span>{

        <span class="hljs-comment">// The MCP tool calls the internal REST API</span>
        String url = UriComponentsBuilder.fromPath(<span class="hljs-string">"/weather/forecast"</span>)
                .queryParam(<span class="hljs-string">"location"</span>, location)
                .queryParam(<span class="hljs-string">"days"</span>, days != <span class="hljs-keyword">null</span> ? days : <span class="hljs-number">1</span>)
                .toUriString();

        ForecastResponse response = restClient.get()
                .uri(url)
                .retrieve()
                .body(ForecastResponse.class);

        // The tool returns a simplified string result <span class="hljs-keyword">for</span> the LLM
        <span class="hljs-keyword">return</span> String.format(<span class="hljs-string">"The forecast for %s is: %s"</span>, response.location(), response.forecast());
    }

    <span class="hljs-comment">// You can reuse the ForecastResponse record from the controller</span>
    <span class="hljs-comment">// or define a simpler one here.</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> record <span class="hljs-title">ForecastResponse</span><span class="hljs-params">(String location, List&lt;DailyForecast&gt; forecast)</span> </span>{}
    <span class="hljs-function"><span class="hljs-keyword">private</span> record <span class="hljs-title">DailyForecast</span><span class="hljs-params">(String date, String condition, Double high, Double low)</span> </span>{}
}
</code></pre>
<h3 id="heading-the-mcp-server-configuration">The MCP Server Configuration</h3>
<p>To make this work, you need a main application class that scans for these <code>@Tool</code> annotations and starts an MCP server.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.ai.tool.ToolCallbackProvider;
<span class="hljs-keyword">import</span> org.springframework.ai.tool.method.MethodToolCallbackProvider;
<span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;
<span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;
<span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;

<span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">McpWeatherServerApplication</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        SpringApplication.run(McpWeatherServerApplication.class, args);
    }

    // This crucial bean tells Spring AI to scan your component <span class="hljs-keyword">for</span> <span class="hljs-meta">@Tool</span> methods
    <span class="hljs-comment">// and expose them as MCP tools.</span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ToolCallbackProvider <span class="hljs-title">weatherTools</span><span class="hljs-params">(WeatherTools weatherTools)</span> </span>{
        <span class="hljs-keyword">return</span> MethodToolCallbackProvider.builder().toolObjects(weatherTools).build();
    }
}
</code></pre>
<p>When this MCP server runs, it connects to an MCP client (like Claude Desktop, or a custom agent orchestrator) and advertises the <code>getForecast</code> tool with its description and input schema.</p>
<h3 id="heading-why-this-matters-for-the-enterprise-architect">Why This Matters for the Enterprise Architect</h3>
<ol>
<li><p><strong>Separation of Concerns:</strong> The core business logic (the <code>WeatherController</code>) remains clean and unaware of AI. It serves all clients. The MCP layer is a specialized adapter.</p>
</li>
<li><p><strong>Security and Governance:</strong> The MCP server can be deployed in a DMZ with stricter security controls. It can handle authentication to the internal API, rate limiting for agent requests, and input sanitization specifically tuned for LLM quirks.</p>
</li>
<li><p><strong>Evolution:</strong> You can version your MCP tools independently of your REST APIs. You could have an MCP tool that composes multiple backend REST calls into a single, agent-friendly action.</p>
</li>
<li><p><strong>Standardization:</strong> By adopting MCP, you stop writing custom, brittle prompts for every new API. The agent discovers capabilities dynamically.</p>
</li>
</ol>
<h3 id="heading-conclusion-the-future-is-multi-client">Conclusion: The Future is Multi-Client</h3>
<p>Your API consumers will no longer be just humans with browsers or developers with API keys. They will be autonomous agents making decisions in milliseconds.</p>
<p>An MCP-first design philosophy doesn't mean abandoning REST or OpenAPI. It means treating them as implementation details for a higher-level capability contract. By designing the <em>tool</em> the agent needs and then building the API to support it, you ensure your enterprise is not just "digitally transformed," but <strong>"intelligence-ready."</strong></p>
<p>Start small. Pick one legacy API, define the MCP tool it should expose, and build a lightweight server for it. This is the first step toward an architecture that doesn't just serve data, but participates in reasoning.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Article 6: Mastering Kestra's Plugin Ecosystem.]]></title><description><![CDATA[Extending Kestra to Every Corner of Your Data Stack.
Introduction: The Power of Plugins
Imagine you're a master chef. You don't just have one knife - you have specialized tools for every task: a paring knife for delicate work, a chef's knife for chop...]]></description><link>https://techwasti.com/article-6-mastering-kestras-plugin-ecosystem</link><guid isPermaLink="true">https://techwasti.com/article-6-mastering-kestras-plugin-ecosystem</guid><category><![CDATA[#Kestra]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[ETL]]></category><category><![CDATA[ELT]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Sun, 15 Feb 2026 18:30:29 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-extending-kestra-to-every-corner-of-your-data-stack"><strong>Extending Kestra to Every Corner of Your Data Stack.</strong></h2>
<h2 id="heading-introduction-the-power-of-plugins"><strong>Introduction: The Power of Plugins</strong></h2>
<p>Imagine you're a master chef. You don't just have one knife - you have specialized tools for every task: a paring knife for delicate work, a chef's knife for chopping, a boning knife for meat, and a bread knife for, well, bread. That's what Kestra's plugin ecosystem gives you: <strong>specialized tools for every data engineering task</strong>.</p>
<p><strong>In this article, you'll learn to:</strong></p>
<ul>
<li><p>Navigate Kestra's vast plugin library (150+ plugins and counting)</p>
</li>
<li><p>Create your own custom plugins for proprietary systems</p>
</li>
<li><p>Build plugin-based architectures for enterprise integration</p>
</li>
<li><p>Debug and optimize plugin performance</p>
</li>
<li><p>Contribute to the open-source plugin ecosystem</p>
</li>
</ul>
<p><strong>Quick Stats:</strong></p>
<ul>
<li><p><strong>150+</strong> official plugins available</p>
</li>
<li><p><strong>1,000+</strong> companies using Kestra plugins</p>
</li>
<li><p><strong>10,000+</strong> plugin downloads per week</p>
</li>
<li><p><strong>0 lines of code</strong> needed for most integrations</p>
</li>
</ul>
<hr />
<h2 id="heading-part-1-the-plugin-architecture"><strong>Part 1: The Plugin Architecture</strong></h2>
<h3 id="heading-understanding-plugin-layers"><strong>Understanding Plugin Layers</strong></h3>
<p>Kestra's plugin architecture is like a LEGO system:</p>
<pre><code class="lang-plaintext">Application Layer (Your Flows)
    ↓
Plugin Interface (Java Interfaces)
    ↓
Plugin Implementation (Built-in/Custom)
    ↓
Target System (Databases, APIs, Cloud Services)
</code></pre>
<h3 id="heading-core-plugin-types"><strong>Core Plugin Types</strong></h3>
<pre><code class="lang-plaintext"># Every plugin belongs to one of these categories
plugins:
  - Input/Output:
    - Filesystems: S3, GCS, Azure Blob, Local, FTP
    - Databases: PostgreSQL, MySQL, Snowflake, BigQuery
    - APIs: REST, GraphQL, SOAP

  - Processing:
    - Scripting: Python, R, Node.js, Bash
    - Data Processing: Spark, Flink, Pandas
    - ML/AI: TensorFlow, PyTorch, Hugging Face

  - Messaging:
    - Queues: Kafka, RabbitMQ, AWS SQS
    - Streaming: Kafka Streams, Apache Pulsar

  - Cloud Services:
    - AWS: S3, Lambda, Glue, Redshift
    - GCP: BigQuery, Cloud Functions, Dataflow
    - Azure: Blob Storage, Data Factory, Synapse

  - Monitoring:
    - Metrics: Prometheus, Datadog, New Relic
    - Logging: ELK, Splunk, CloudWatch
    - Alerting: Slack, PagerDuty, Email

  - Custom:
    - Enterprise Systems: SAP, Salesforce, Oracle
    - Internal APIs: Your company's systems
    - Legacy Systems: Mainframes, AS/400
</code></pre>
<h3 id="heading-plugin-anatomy-what-makes-a-good-plugin"><strong>Plugin Anatomy: What Makes a Good Plugin?</strong></h3>
<pre><code class="lang-java"><span class="hljs-comment">// The anatomy of a Kestra plugin (simplified)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyPlugin</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Task</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">FlowableTask</span> </span>{

    <span class="hljs-comment">// 1. Configuration (YAML properties)</span>
    <span class="hljs-meta">@PluginProperty</span> <span class="hljs-keyword">private</span> String connectionString;
    <span class="hljs-meta">@PluginProperty</span> <span class="hljs-keyword">private</span> Integer timeout;

    <span class="hljs-comment">// 2. Execution logic</span>
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;TaskRun&gt; <span class="hljs-title">run</span><span class="hljs-params">(RunContext runContext)</span> <span class="hljs-keyword">throws</span> Exception </span>{
        <span class="hljs-comment">// Your plugin logic here</span>
        <span class="hljs-keyword">return</span> List.of(TaskRun.of(<span class="hljs-keyword">this</span>, Outputs.of(<span class="hljs-string">"result"</span>, <span class="hljs-string">"success"</span>)));
    }

    <span class="hljs-comment">// 3. Input/Output handling</span>
    <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Output</span> </span>{
        <span class="hljs-function">String <span class="hljs-title">result</span><span class="hljs-params">()</span></span>;
    }
}
</code></pre>
<hr />
<h2 id="heading-part-2-using-built-in-plugins"><strong>Part 2: Using Built-in Plugins</strong></h2>
<h3 id="heading-plugin-discovery-and-installation"><strong>Plugin Discovery and Installation</strong></h3>
<pre><code class="lang-plaintext"># List available plugins
kestra plugins list

# Search for specific plugins
kestra plugins search --query "postgres"

# Install a plugin
kestra plugins install kestra-plugin-jdbc-postgresql

# View installed plugins
kestra plugins installed

# Update all plugins
kestra plugins update-all
</code></pre>
<h3 id="heading-database-plugins-deep-dive"><strong>Database Plugins Deep Dive</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/database-demo.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">database-plugin-showcase</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">demo.plugins</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Showcase</span> <span class="hljs-string">of</span> <span class="hljs-string">database</span> <span class="hljs-string">plugins</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># PostgreSQL - Full-featured SQL database</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">postgres_example</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:postgresql://localhost:5432/mydb</span>
    <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('PG_USER') }}</span>"</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('PG_PASS') }}</span>"</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      -- Parameterized query
      SELECT * FROM users 
      WHERE created_at &gt; '{{ execution.startDate | dateAdd(-7, 'DAYS') }}'
        AND status = '{{ inputs.user_status }}'
</span>    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">fetchSize:</span> <span class="hljs-number">1000</span>
    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

  <span class="hljs-comment"># Snowflake - Cloud data warehouse  </span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">snowflake_example</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Query</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:snowflake://account.snowflakecomputing.com</span>
    <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_USER') }}</span>"</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_PASS') }}</span>"</span>
    <span class="hljs-attr">role:</span> <span class="hljs-string">SYSADMIN</span>
    <span class="hljs-attr">warehouse:</span> <span class="hljs-string">COMPUTE_WH</span>
    <span class="hljs-attr">database:</span> <span class="hljs-string">PRODUCTION</span>
    <span class="hljs-attr">schema:</span> <span class="hljs-string">ANALYTICS</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      COPY INTO @my_stage/users.parquet
      FROM (
        SELECT * FROM users 
        WHERE DAY = '{{ execution.startDate | date('yyyy-MM-dd') }}'
      )
      FILE_FORMAT = (TYPE = PARQUET)
</span>    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

  <span class="hljs-comment"># BigQuery - Serverless data warehouse</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">bigquery_example</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.gcp.bigquery.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      SELECT 
        user_id,
        COUNT(*) as session_count,
        SUM(duration) as total_duration
      FROM `project.dataset.sessions`
      WHERE DATE(timestamp) = '{{ execution.startDate | date('yyyy-MM-dd') }}'
      GROUP BY user_id
</span>    <span class="hljs-attr">destinationTable:</span>
      <span class="hljs-attr">projectId:</span> <span class="hljs-string">my-project</span>
      <span class="hljs-attr">datasetId:</span> <span class="hljs-string">results</span>
      <span class="hljs-attr">tableId:</span> <span class="hljs-string">daily_sessions</span>
    <span class="hljs-attr">writeDisposition:</span> <span class="hljs-string">WRITE_TRUNCATE</span>
    <span class="hljs-attr">createDisposition:</span> <span class="hljs-string">CREATE_IF_NEEDED</span>

  <span class="hljs-comment"># ClickHouse - OLAP database</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">clickhouse_example</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.clickhouse.Query</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:clickhouse://localhost:8123/analytics</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      SELECT 
        toStartOfDay(timestamp) as day,
        uniq(user_id) as daily_active_users,
        count(*) as events
      FROM events
      WHERE timestamp &gt;= '{{ execution.startDate | dateAdd(-30, 'DAYS') }}'
      GROUP BY day
      ORDER BY day
</span>    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>

  <span class="hljs-comment"># Multi-database Join (Virtual Data Warehouse)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cross_database_join</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Join data from multiple databases"</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">postgres_data.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.postgres_example.uri }}</span>"</span>
      <span class="hljs-attr">snowflake_data.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.snowflake_example.uri }}</span>"</span>
      <span class="hljs-attr">bigquery_data.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.bigquery_example.uri }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import json
</span>
      <span class="hljs-comment"># Load from different sources</span>
      <span class="hljs-string">pg_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_json('postgres_data.json')</span>
      <span class="hljs-string">sf_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_json('snowflake_data.json')</span>
      <span class="hljs-string">bq_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_json('bigquery_data.json')</span>

      <span class="hljs-comment"># Perform cross-database join</span>
      <span class="hljs-string">merged</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.merge(</span>
          <span class="hljs-string">pg_df,</span> 
          <span class="hljs-string">sf_df,</span> 
          <span class="hljs-string">on='user_id',</span>
          <span class="hljs-string">how='left'</span>
      <span class="hljs-string">).merge(</span>
          <span class="hljs-string">bq_df,</span>
          <span class="hljs-string">on='user_id',</span>
          <span class="hljs-string">how='left'</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Save result</span>
      <span class="hljs-string">merged.to_parquet('cross_database_result.parquet',</span> <span class="hljs-string">index=False)</span>
      <span class="hljs-string">print(f"Merged</span> {<span class="hljs-string">len(merged)</span>} <span class="hljs-string">records</span> <span class="hljs-string">from</span> <span class="hljs-number">3</span> <span class="hljs-string">databases")</span>
</code></pre>
<h3 id="heading-cloud-storage-plugins"><strong>Cloud Storage Plugins</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/cloud-storage.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">cloud-storage-operations</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">demo.plugins</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># AWS S3 Operations</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">s3_operations</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">upload_to_s3</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.aws.s3.Upload</span>
        <span class="hljs-attr">from:</span> <span class="hljs-string">"data/output.parquet"</span>
        <span class="hljs-attr">bucket:</span> <span class="hljs-string">my-data-lake</span>
        <span class="hljs-attr">key:</span> <span class="hljs-string">"raw/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy/MM/dd') }}</span>/data.parquet"</span>
        <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>
        <span class="hljs-attr">accessKeyId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('AWS_ACCESS_KEY') }}</span>"</span>
        <span class="hljs-attr">secretKeyId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('AWS_SECRET_KEY') }}</span>"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">list_s3_files</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.aws.s3.List</span>
        <span class="hljs-attr">bucket:</span> <span class="hljs-string">my-data-lake</span>
        <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>
        <span class="hljs-attr">prefix:</span> <span class="hljs-string">"raw/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy/MM/dd') }}</span>/"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">sync_s3_to_local</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.aws.s3.Download</span>
        <span class="hljs-attr">bucket:</span> <span class="hljs-string">my-data-lake</span>
        <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.list_s3_files.uris[0] }}</span>"</span>
        <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>

  <span class="hljs-comment"># Google Cloud Storage</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">gcs_operations</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.gcp.gcs.Upload</span>
    <span class="hljs-attr">from:</span> <span class="hljs-string">"data/processed.parquet"</span>
    <span class="hljs-attr">bucket:</span> <span class="hljs-string">my-gcp-bucket</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">"processed/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy-MM-dd') }}</span>.parquet"</span>
    <span class="hljs-attr">projectId:</span> <span class="hljs-string">my-gcp-project</span>

  <span class="hljs-comment"># Azure Blob Storage</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">azure_operations</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.azure.blob.Upload</span>
    <span class="hljs-attr">from:</span> <span class="hljs-string">"data/archive.parquet"</span>
    <span class="hljs-attr">connectionString:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('AZURE_CONNECTION_STRING') }}</span>"</span>
    <span class="hljs-attr">container:</span> <span class="hljs-string">my-container</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">"archive/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy/MM') }}</span>/data.parquet"</span>

  <span class="hljs-comment"># Multi-cloud synchronization</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">multi_cloud_sync</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">sync_to_aws</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.aws.s3.Copy</span>
        <span class="hljs-attr">from:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.gcs_operations.uri }}</span>"</span>
        <span class="hljs-attr">to:</span> <span class="hljs-string">"s3://my-data-lake/gcs-mirror/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy-MM-dd') }}</span>.parquet"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">sync_to_azure</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.azure.blob.Copy</span>
        <span class="hljs-attr">from:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.gcs_operations.uri }}</span>"</span>
        <span class="hljs-attr">to:</span> <span class="hljs-string">"https://myaccount.blob.core.windows.net/mycontainer/gcs-mirror/data.parquet"</span>
</code></pre>
<h3 id="heading-messaging-and-streaming-plugins"><strong>Messaging and Streaming Plugins</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/messaging-demo.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">messaging-patterns</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">demo.plugins</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Kafka Producer</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">produce_to_kafka</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Produce</span>
    <span class="hljs-attr">topic:</span> <span class="hljs-string">user-events</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.user_id }}</span>"</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">|
      {
        "event_type": "{{ inputs.event_type }}",
        "user_id": "{{ inputs.user_id }}",
        "timestamp": "{{ now() }}",
        "properties": {{ inputs.properties | tojson }}
      }
</span>    <span class="hljs-attr">properties:</span>
      <span class="hljs-attr">bootstrap.servers:</span> <span class="hljs-string">"kafka-broker:9092"</span>
      <span class="hljs-attr">acks:</span> <span class="hljs-string">"all"</span>
      <span class="hljs-attr">compression.type:</span> <span class="hljs-string">"snappy"</span>
    <span class="hljs-attr">serializer:</span> <span class="hljs-string">STRING</span>

  <span class="hljs-comment"># Kafka Consumer (Stream Processing)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">consume_from_kafka</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Consume</span>
    <span class="hljs-attr">topic:</span> <span class="hljs-string">user-events</span>
    <span class="hljs-attr">groupId:</span> <span class="hljs-string">"etl-processor-<span class="hljs-template-variable">{{ execution.startDate | date('yyyy-MM-dd') }}</span>"</span>
    <span class="hljs-attr">maxRecords:</span> <span class="hljs-number">1000</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT1M</span>
    <span class="hljs-attr">properties:</span>
      <span class="hljs-attr">bootstrap.servers:</span> <span class="hljs-string">"kafka-broker:9092"</span>
      <span class="hljs-attr">auto.offset.reset:</span> <span class="hljs-string">"earliest"</span>

  <span class="hljs-comment"># RabbitMQ</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">rabbitmq_example</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.amqp.Write</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"amqp://user:pass@rabbitmq:5672"</span>
    <span class="hljs-attr">exchange:</span> <span class="hljs-string">events</span>
    <span class="hljs-attr">routingKey:</span> <span class="hljs-string">"user.<span class="hljs-template-variable">{{ inputs.user_type }}</span>"</span>
    <span class="hljs-attr">content:</span> <span class="hljs-string">|
      {{ inputs.event_data | tojson }}
</span>    <span class="hljs-attr">contentType:</span> <span class="hljs-string">"application/json"</span>

  <span class="hljs-comment"># AWS SQS</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">sqs_example</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.aws.sqs.Send</span>
    <span class="hljs-attr">queueUrl:</span> <span class="hljs-string">"https://sqs.us-east-1.amazonaws.com/123456789012/my-queue"</span>
    <span class="hljs-attr">messageBody:</span> <span class="hljs-string">|
      {{ inputs.message | tojson }}
</span>    <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>

  <span class="hljs-comment"># Real-time Data Pipeline</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">real_time_pipeline</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">consume_stream</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Consume</span>
        <span class="hljs-attr">topic:</span> <span class="hljs-string">clickstream</span>
        <span class="hljs-attr">groupId:</span> <span class="hljs-string">real-time-processor</span>
        <span class="hljs-attr">maxRecords:</span> <span class="hljs-number">100</span>
        <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT10S</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_in_realtime</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
        <span class="hljs-attr">inputFiles:</span>
          <span class="hljs-attr">messages.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.consume_stream.uri }}</span>"</span>
        <span class="hljs-attr">script:</span> <span class="hljs-string">|
          import json
          import pandas as pd
          from datetime import datetime
</span>
          <span class="hljs-string">with</span> <span class="hljs-string">open('messages.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
              <span class="hljs-string">messages</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

          <span class="hljs-comment"># Real-time processing</span>
          <span class="hljs-string">events</span> <span class="hljs-string">=</span> []
          <span class="hljs-attr">for msg in messages:</span>
              <span class="hljs-string">event</span> <span class="hljs-string">=</span> <span class="hljs-string">json.loads(msg['value'])</span>

              <span class="hljs-comment"># Enrich in real-time</span>
              <span class="hljs-string">event['processed_at']</span> <span class="hljs-string">=</span> <span class="hljs-string">datetime.now().isoformat()</span>
              <span class="hljs-string">event['hour_of_day']</span> <span class="hljs-string">=</span> <span class="hljs-string">datetime.fromisoformat(event['timestamp']).hour</span>
              <span class="hljs-string">event['is_peak']</span> <span class="hljs-string">=</span> <span class="hljs-number">9</span> <span class="hljs-string">&lt;=</span> <span class="hljs-string">event['hour_of_day']</span> <span class="hljs-string">&lt;=</span> <span class="hljs-number">17</span>

              <span class="hljs-string">events.append(event)</span>

          <span class="hljs-comment"># Batch write for efficiency</span>
          <span class="hljs-attr">if events:</span>
              <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(events)</span>
              <span class="hljs-string">df.to_parquet(f'processed_{datetime.now().strftime("%H%M%S")}.parquet')</span>
              <span class="hljs-string">print(f"Processed</span> {<span class="hljs-string">len(events)</span>} <span class="hljs-string">events</span> <span class="hljs-string">in</span> <span class="hljs-string">real-time")</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">produce_results</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Produce</span>
        <span class="hljs-attr">topic:</span> <span class="hljs-string">processed-events</span>
        <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.startDate | date('yyyy-MM-dd HH') }}</span>"</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.process_in_realtime.outputFiles | tojson }}</span>"</span>
        <span class="hljs-attr">conditions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
            <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.process_in_realtime.state.current == 'SUCCESS' }}</span>"</span>
</code></pre>
<h3 id="heading-scripting-and-data-processing-plugins"><strong>Scripting and Data Processing Plugins</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/scripting-demo.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">multi-language-scripts</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">demo.plugins</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Python with dependencies</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">python_with_deps</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Python script with custom dependencies"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import numpy as np
      from scipy import stats
      import requests
</span>
      <span class="hljs-comment"># Your data science code here</span>
      <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame({</span>
          <span class="hljs-attr">'x':</span> <span class="hljs-string">np.random.normal(0,</span> <span class="hljs-number">1</span><span class="hljs-string">,</span> <span class="hljs-number">1000</span><span class="hljs-string">),</span>
          <span class="hljs-attr">'y':</span> <span class="hljs-string">np.random.normal(0,</span> <span class="hljs-number">1</span><span class="hljs-string">,</span> <span class="hljs-number">1000</span><span class="hljs-string">)</span>
      <span class="hljs-string">})</span>

      <span class="hljs-string">correlation</span> <span class="hljs-string">=</span> <span class="hljs-string">data['x'].corr(data['y'])</span>
      <span class="hljs-string">print(f"Correlation:</span> {<span class="hljs-string">correlation:.3f</span>}<span class="hljs-string">")

      # Save results
      data.to_parquet('analysis_result.parquet', index=False)
    docker:
      image: "</span><span class="hljs-string">kestra/kestra-python:latest"</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">/tmp:/tmp</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">config.yaml:</span> <span class="hljs-string">|
        dataset:
          name: "sample_data"
          version: "1.0"
</span>    <span class="hljs-attr">outputFiles:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"*.parquet"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"*.csv"</span>

  <span class="hljs-comment"># R for statistical analysis</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">r_analysis</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.r.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      library(dplyr)
      library(ggplot2)
      library(jsonlite)
</span>
      <span class="hljs-comment"># Load data</span>
      <span class="hljs-string">data</span> <span class="hljs-string">&lt;-</span> <span class="hljs-string">read.csv("input_data.csv")</span>

      <span class="hljs-comment"># Statistical analysis</span>
      <span class="hljs-string">result</span> <span class="hljs-string">&lt;-</span> <span class="hljs-string">data</span> <span class="hljs-string">%&gt;%</span>
        <span class="hljs-string">group_by(category)</span> <span class="hljs-string">%&gt;%</span>
        <span class="hljs-string">summarise(</span>
          <span class="hljs-string">mean</span> <span class="hljs-string">=</span> <span class="hljs-string">mean(value),</span>
          <span class="hljs-string">sd</span> <span class="hljs-string">=</span> <span class="hljs-string">sd(value),</span>
          <span class="hljs-string">n</span> <span class="hljs-string">=</span> <span class="hljs-string">n()</span>
        <span class="hljs-string">)</span>

      <span class="hljs-comment"># Create visualization</span>
      <span class="hljs-string">plot</span> <span class="hljs-string">&lt;-</span> <span class="hljs-string">ggplot(data,</span> <span class="hljs-string">aes(x=category,</span> <span class="hljs-string">y=value))</span> <span class="hljs-string">+</span>
        <span class="hljs-string">geom_boxplot()</span> <span class="hljs-string">+</span>
        <span class="hljs-string">theme_minimal()</span>

      <span class="hljs-string">ggsave("boxplot.png",</span> <span class="hljs-string">plot,</span> <span class="hljs-string">width=8,</span> <span class="hljs-string">height=6)</span>

      <span class="hljs-comment"># Output results</span>
      <span class="hljs-string">write_json(result,</span> <span class="hljs-string">"analysis_result.json"</span><span class="hljs-string">)</span>
      <span class="hljs-string">cat("Analysis</span> <span class="hljs-string">complete\n")</span>
    <span class="hljs-attr">docker:</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">rocker/tidyverse:latest</span>

  <span class="hljs-comment"># Node.js for API interactions</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">nodejs_api_client</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.node.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      const axios = require('axios');
      const fs = require('fs').promises;
</span>
      <span class="hljs-string">async</span> <span class="hljs-string">function</span> <span class="hljs-string">fetchData()</span> {
          <span class="hljs-string">const</span> <span class="hljs-string">response</span> <span class="hljs-string">=</span> <span class="hljs-string">await</span> <span class="hljs-string">axios.get('https://api.example.com/data');</span>
          <span class="hljs-string">const</span> <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">response.data;</span>

          <span class="hljs-string">//</span> <span class="hljs-string">Transform</span> <span class="hljs-string">data</span>
          <span class="hljs-string">const</span> <span class="hljs-string">transformed</span> <span class="hljs-string">=</span> <span class="hljs-string">data.map(item</span> <span class="hljs-string">=&gt;</span> <span class="hljs-string">(</span>{
              <span class="hljs-attr">id:</span> <span class="hljs-string">item.id</span>,
              <span class="hljs-attr">name:</span> <span class="hljs-string">item.name.toUpperCase()</span>,
              <span class="hljs-attr">timestamp:</span> <span class="hljs-string">new</span> <span class="hljs-string">Date().toISOString()</span>
          }<span class="hljs-string">));</span>

          <span class="hljs-string">//</span> <span class="hljs-string">Save</span> <span class="hljs-string">to</span> <span class="hljs-string">file</span>
          <span class="hljs-string">await</span> <span class="hljs-string">fs.writeFile(</span>
              <span class="hljs-string">'api_data.json'</span>,
              <span class="hljs-string">JSON.stringify(transformed</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span><span class="hljs-string">)</span>
          <span class="hljs-string">);</span>

          <span class="hljs-string">console.log(`Fetched</span> <span class="hljs-string">$</span>{<span class="hljs-string">transformed.length</span>} <span class="hljs-string">records`);</span>
      }

      <span class="hljs-string">fetchData().catch(console.error);</span>
    <span class="hljs-attr">docker:</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">node:18-alpine</span>

  <span class="hljs-comment"># Bash for system operations</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">bash_operations</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.shell.Commands</span>
    <span class="hljs-attr">commands:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">find</span> <span class="hljs-string">/data</span> <span class="hljs-string">-name</span> <span class="hljs-string">"*.parquet"</span> <span class="hljs-string">-type</span> <span class="hljs-string">f</span> <span class="hljs-string">-mtime</span> <span class="hljs-number">-7</span> <span class="hljs-string">|</span> <span class="hljs-string">wc</span> <span class="hljs-string">-l</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">du</span> <span class="hljs-string">-sh</span> <span class="hljs-string">/data/*</span> <span class="hljs-string">|</span> <span class="hljs-string">sort</span> <span class="hljs-string">-hr</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Processing started at $(date)"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">tar</span> <span class="hljs-string">-czf</span> <span class="hljs-string">archive_$(date</span> <span class="hljs-string">+%Y%m%d_%H%M%S).tar.gz</span> <span class="hljs-string">/data/input</span>
    <span class="hljs-attr">exitOnFailed:</span> <span class="hljs-literal">false</span>

  <span class="hljs-comment"># SQL-based transformation</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">sql_transformation</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.duckdb.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      -- Load data from multiple formats
      CREATE TABLE users AS 
      SELECT * FROM read_parquet('users.parquet');
</span>
      <span class="hljs-string">CREATE</span> <span class="hljs-string">TABLE</span> <span class="hljs-string">orders</span> <span class="hljs-string">AS</span>
      <span class="hljs-string">SELECT</span> <span class="hljs-string">*</span> <span class="hljs-string">FROM</span> <span class="hljs-string">read_csv('orders.csv');</span>

      <span class="hljs-string">--</span> <span class="hljs-string">Complex</span> <span class="hljs-string">transformation</span>
      <span class="hljs-string">WITH</span> <span class="hljs-string">user_stats</span> <span class="hljs-string">AS</span> <span class="hljs-string">(</span>
        <span class="hljs-string">SELECT</span> 
          <span class="hljs-string">u.user_id,</span>
          <span class="hljs-string">u.join_date,</span>
          <span class="hljs-string">COUNT(o.order_id)</span> <span class="hljs-string">as</span> <span class="hljs-string">order_count,</span>
          <span class="hljs-string">SUM(o.amount)</span> <span class="hljs-string">as</span> <span class="hljs-string">total_spent,</span>
          <span class="hljs-string">AVG(o.amount)</span> <span class="hljs-string">as</span> <span class="hljs-string">avg_order_value</span>
        <span class="hljs-string">FROM</span> <span class="hljs-string">users</span> <span class="hljs-string">u</span>
        <span class="hljs-string">LEFT</span> <span class="hljs-string">JOIN</span> <span class="hljs-string">orders</span> <span class="hljs-string">o</span> <span class="hljs-string">ON</span> <span class="hljs-string">u.user_id</span> <span class="hljs-string">=</span> <span class="hljs-string">o.user_id</span>
        <span class="hljs-string">GROUP</span> <span class="hljs-string">BY</span> <span class="hljs-string">u.user_id,</span> <span class="hljs-string">u.join_date</span>
      <span class="hljs-string">)</span>
      <span class="hljs-string">SELECT</span> <span class="hljs-string">*</span> <span class="hljs-string">FROM</span> <span class="hljs-string">user_stats</span>
      <span class="hljs-string">ORDER</span> <span class="hljs-string">BY</span> <span class="hljs-string">total_spent</span> <span class="hljs-string">DESC;</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
</code></pre>
<h3 id="heading-machine-learning-plugins"><strong>Machine Learning Plugins</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/ml-pipeline.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">ml-training-pipeline</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">demo.ml</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Data Preparation</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">prepare_training_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      from sklearn.model_selection import train_test_split
      import joblib
</span>
      <span class="hljs-comment"># Load and prepare data</span>
      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_parquet('raw_data.parquet')</span>

      <span class="hljs-comment"># Feature engineering</span>
      <span class="hljs-string">df['feature1']</span> <span class="hljs-string">=</span> <span class="hljs-string">df['value1']</span> <span class="hljs-string">*</span> <span class="hljs-string">df['value2']</span>
      <span class="hljs-string">df['feature2']</span> <span class="hljs-string">=</span> <span class="hljs-string">df['value3'].apply(lambda</span> <span class="hljs-attr">x:</span> <span class="hljs-number">1</span> <span class="hljs-string">if</span> <span class="hljs-string">x</span> <span class="hljs-string">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span><span class="hljs-string">)</span>

      <span class="hljs-comment"># Split data</span>
      <span class="hljs-string">X</span> <span class="hljs-string">=</span> <span class="hljs-string">df[['feature1',</span> <span class="hljs-string">'feature2'</span><span class="hljs-string">,</span> <span class="hljs-string">'value4'</span><span class="hljs-string">]]</span>
      <span class="hljs-string">y</span> <span class="hljs-string">=</span> <span class="hljs-string">df['target']</span>

      <span class="hljs-string">X_train,</span> <span class="hljs-string">X_test,</span> <span class="hljs-string">y_train,</span> <span class="hljs-string">y_test</span> <span class="hljs-string">=</span> <span class="hljs-string">train_test_split(</span>
          <span class="hljs-string">X,</span> <span class="hljs-string">y,</span> <span class="hljs-string">test_size=0.2,</span> <span class="hljs-string">random_state=42</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Save prepared data</span>
      <span class="hljs-string">joblib.dump({</span>
          <span class="hljs-attr">'X_train':</span> <span class="hljs-string">X_train,</span>
          <span class="hljs-attr">'X_test':</span> <span class="hljs-string">X_test,</span>
          <span class="hljs-attr">'y_train':</span> <span class="hljs-string">y_train,</span>
          <span class="hljs-attr">'y_test':</span> <span class="hljs-string">y_test,</span>
          <span class="hljs-attr">'feature_names':</span> <span class="hljs-string">X.columns.tolist()</span>
      <span class="hljs-string">},</span> <span class="hljs-string">'prepared_data.pkl'</span><span class="hljs-string">)</span>

      <span class="hljs-string">print(f"Prepared</span> <span class="hljs-attr">data:</span> {<span class="hljs-string">len(X_train)</span>} <span class="hljs-string">training,</span> {<span class="hljs-string">len(X_test)</span>} <span class="hljs-string">test</span> <span class="hljs-string">samples")</span>

  <span class="hljs-comment"># Model Training with TensorFlow</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">train_tensorflow_model</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">data.pkl:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.prepare_training_data.outputFiles['prepared_data.pkl'] }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import tensorflow as tf
      from tensorflow import keras
      import joblib
      import numpy as np
</span>
      <span class="hljs-comment"># Load prepared data</span>
      <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">joblib.load('data.pkl')</span>
      <span class="hljs-string">X_train,</span> <span class="hljs-string">X_test,</span> <span class="hljs-string">y_train,</span> <span class="hljs-string">y_test</span> <span class="hljs-string">=</span> <span class="hljs-string">(</span>
          <span class="hljs-string">data['X_train'],</span> <span class="hljs-string">data['X_test'],</span> 
          <span class="hljs-string">data['y_train'],</span> <span class="hljs-string">data['y_test']</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Build model</span>
      <span class="hljs-string">model</span> <span class="hljs-string">=</span> <span class="hljs-string">keras.Sequential([</span>
          <span class="hljs-string">keras.layers.Dense(64,</span> <span class="hljs-string">activation='relu',</span> <span class="hljs-string">input_shape=(X_train.shape[1],)),</span>
          <span class="hljs-string">keras.layers.Dropout(0.2),</span>
          <span class="hljs-string">keras.layers.Dense(32,</span> <span class="hljs-string">activation='relu'),</span>
          <span class="hljs-string">keras.layers.Dense(1,</span> <span class="hljs-string">activation='sigmoid')</span>
      <span class="hljs-string">])</span>

      <span class="hljs-string">model.compile(</span>
          <span class="hljs-string">optimizer='adam',</span>
          <span class="hljs-string">loss='binary_crossentropy',</span>
          <span class="hljs-string">metrics=['accuracy']</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Train model</span>
      <span class="hljs-string">history</span> <span class="hljs-string">=</span> <span class="hljs-string">model.fit(</span>
          <span class="hljs-string">X_train,</span> <span class="hljs-string">y_train,</span>
          <span class="hljs-string">validation_split=0.2,</span>
          <span class="hljs-string">epochs=10,</span>
          <span class="hljs-string">batch_size=32,</span>
          <span class="hljs-string">verbose=1</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Evaluate</span>
      <span class="hljs-string">test_loss,</span> <span class="hljs-string">test_acc</span> <span class="hljs-string">=</span> <span class="hljs-string">model.evaluate(X_test,</span> <span class="hljs-string">y_test,</span> <span class="hljs-string">verbose=0)</span>
      <span class="hljs-string">print(f"Test</span> <span class="hljs-attr">accuracy:</span> {<span class="hljs-string">test_acc:.4f</span>}<span class="hljs-string">")

      # Save model
      model.save('trained_model.h5')

      # Save metrics
      metrics = {
          'test_accuracy': float(test_acc),
          'test_loss': float(test_loss),
          'training_history': history.history
      }

      import json
      with open('model_metrics.json', 'w') as f:
          json.dump(metrics, f)

    docker:
      image: tensorflow/tensorflow:2.13.0
      gpu: true  # Enable GPU if available

  # Model Serving with Triton
  - id: deploy_model_triton
    type: io.kestra.plugin.triton.DeployModel
    modelRepository: "</span><span class="hljs-string">/models"</span>
    <span class="hljs-attr">modelName:</span> <span class="hljs-string">"customer-churn"</span>
    <span class="hljs-attr">modelVersion:</span> <span class="hljs-string">"1"</span>
    <span class="hljs-attr">modelFile:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.train_tensorflow_model.outputFiles['trained_model.h5'] }}</span>"</span>
    <span class="hljs-attr">config:</span> <span class="hljs-string">|
      platform: "tensorflow_savedmodel"
      max_batch_size: 64
      input [
        {
          name: "input"
          data_type: TYPE_FP32
          dims: [ -1, 3 ]
        }
      ]
      output [
        {
          name: "output"
          data_type: TYPE_FP32
          dims: [ -1, 1 ]
        }
      ]
</span>
  <span class="hljs-comment"># ML Monitoring with Evidently</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">monitor_model_drift</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import numpy as np
      from evidently.report import Report
      from evidently.metric_preset import DataDriftPreset
      import joblib
</span>
      <span class="hljs-comment"># Load reference data (training data)</span>
      <span class="hljs-string">ref_data</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_parquet('reference_data.parquet')</span>

      <span class="hljs-comment"># Load current data (production data)</span>
      <span class="hljs-string">curr_data</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_parquet('current_data.parquet')</span>

      <span class="hljs-comment"># Generate drift report</span>
      <span class="hljs-string">drift_report</span> <span class="hljs-string">=</span> <span class="hljs-string">Report(metrics=[DataDriftPreset()])</span>
      <span class="hljs-string">drift_report.run(</span>
          <span class="hljs-string">reference_data=ref_data,</span>
          <span class="hljs-string">current_data=curr_data</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Save report</span>
      <span class="hljs-string">drift_report.save_html('data_drift_report.html')</span>

      <span class="hljs-comment"># Extract metrics</span>
      <span class="hljs-string">drift_metrics</span> <span class="hljs-string">=</span> <span class="hljs-string">drift_report.as_dict()</span>
      <span class="hljs-string">drift_score</span> <span class="hljs-string">=</span> <span class="hljs-string">drift_metrics['metrics'][0]['result']['dataset_drift']</span>

      <span class="hljs-string">print(f"Data</span> <span class="hljs-attr">drift detected:</span> {<span class="hljs-string">drift_score</span>}<span class="hljs-string">")

      # Alert if drift exceeds threshold
      if drift_score:
          print("</span><span class="hljs-attr">ALERT:</span> <span class="hljs-string">Significant</span> <span class="hljs-string">data</span> <span class="hljs-string">drift</span> <span class="hljs-string">detected!")</span>
</code></pre>
<hr />
<h2 id="heading-part-3-creating-custom-plugins"><strong>Part 3: Creating Custom Plugins</strong></h2>
<h3 id="heading-plugin-development-environment-setup"><strong>Plugin Development Environment Setup</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Create plugin project</span>
mvn archetype:generate \
  -DarchetypeGroupId=io.kestra \
  -DarchetypeArtifactId=kestra-plugin-archetype \
  -DarchetypeVersion=0.15.0 \
  -DgroupId=com.company.kestra \
  -DartifactId=kestra-plugin-custom \
  -Dversion=1.0.0 \
  -Dpackage=com.company.kestra.plugin.custom

<span class="hljs-comment"># Build plugin</span>
<span class="hljs-built_in">cd</span> kestra-plugin-custom
mvn clean package

<span class="hljs-comment"># Install locally</span>
mvn install

<span class="hljs-comment"># Test plugin</span>
mvn <span class="hljs-built_in">test</span>

<span class="hljs-comment"># Deploy to Kestra</span>
cp target/kestra-plugin-custom-1.0.0.jar /path/to/kestra/plugins/
</code></pre>
<h3 id="heading-simple-custom-plugin-example"><strong>Simple Custom Plugin Example</strong></h3>
<pre><code class="lang-java"><span class="hljs-comment">// src/main/java/com/company/kestra/plugin/custom/HelloWorld.java</span>
<span class="hljs-keyword">package</span> com.company.kestra.plugin.custom;

<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.Example;
<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.Plugin;
<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.PluginProperty;
<span class="hljs-keyword">import</span> io.kestra.core.models.tasks.RunnableTask;
<span class="hljs-keyword">import</span> io.kestra.core.models.tasks.Task;
<span class="hljs-keyword">import</span> io.kestra.core.runners.RunContext;
<span class="hljs-keyword">import</span> io.swagger.v3.oas.annotations.media.Schema;
<span class="hljs-keyword">import</span> lombok.*;
<span class="hljs-keyword">import</span> lombok.experimental.SuperBuilder;

<span class="hljs-keyword">import</span> javax.validation.constraints.NotNull;

<span class="hljs-meta">@SuperBuilder</span>
<span class="hljs-meta">@ToString</span>
<span class="hljs-meta">@EqualsAndHashCode</span>
<span class="hljs-meta">@Getter</span>
<span class="hljs-meta">@NoArgsConstructor</span>
<span class="hljs-meta">@Plugin(
    examples = {
        @Example(
            title = "Say hello to someone",
            code = {
                "id: hello",
                "type: com.company.kestra.plugin.custom.HelloWorld",
                "name: John"
            }
        )
    }
)</span>
<span class="hljs-meta">@Schema(
    title = "Hello World Task",
    description = "A simple custom plugin that greets someone"
)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorld</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Task</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">RunnableTask</span>&lt;<span class="hljs-title">HelloWorld</span>.<span class="hljs-title">Output</span>&gt; </span>{

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "Name to greet")</span>
    <span class="hljs-meta">@NotNull</span>
    <span class="hljs-keyword">private</span> String name;

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "Greeting message")</span>
    <span class="hljs-keyword">private</span> String message;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Output <span class="hljs-title">run</span><span class="hljs-params">(RunContext runContext)</span> <span class="hljs-keyword">throws</span> Exception </span>{
        String greeting = message != <span class="hljs-keyword">null</span> ? message : <span class="hljs-string">"Hello"</span>;
        String fullMessage = greeting + <span class="hljs-string">", "</span> + name + <span class="hljs-string">"!"</span>;

        <span class="hljs-comment">// Log the greeting</span>
        runContext.logger().info(fullMessage);

        <span class="hljs-comment">// You can also render variables</span>
        String renderedName = runContext.render(name);

        <span class="hljs-keyword">return</span> Output.builder()
            .greeting(fullMessage)
            .renderedName(renderedName)
            .executionTime(System.currentTimeMillis())
            .build();
    }

    <span class="hljs-meta">@Builder</span>
    <span class="hljs-meta">@Getter</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Output</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">io</span>.<span class="hljs-title">kestra</span>.<span class="hljs-title">core</span>.<span class="hljs-title">models</span>.<span class="hljs-title">tasks</span>.<span class="hljs-title">Output</span> </span>{
        <span class="hljs-meta">@Schema(title = "The greeting message")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String greeting;

        <span class="hljs-meta">@Schema(title = "Rendered name")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String renderedName;

        <span class="hljs-meta">@Schema(title = "Execution time in milliseconds")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Long executionTime;
    }
}
</code></pre>
<h3 id="heading-database-query-plugin-example"><strong>Database Query Plugin Example</strong></h3>
<pre><code class="lang-java"><span class="hljs-comment">// src/main/java/com/company/kestra/plugin/custom/CustomDatabaseQuery.java</span>
<span class="hljs-keyword">package</span> com.company.kestra.plugin.custom;

<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.Example;
<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.Plugin;
<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.PluginProperty;
<span class="hljs-keyword">import</span> io.kestra.core.models.tasks.RunnableTask;
<span class="hljs-keyword">import</span> io.kestra.core.models.tasks.Task;
<span class="hljs-keyword">import</span> io.kestra.core.runners.RunContext;
<span class="hljs-keyword">import</span> io.swagger.v3.oas.annotations.media.Schema;
<span class="hljs-keyword">import</span> lombok.*;
<span class="hljs-keyword">import</span> lombok.experimental.SuperBuilder;

<span class="hljs-keyword">import</span> javax.validation.constraints.NotNull;
<span class="hljs-keyword">import</span> java.sql.*;
<span class="hljs-keyword">import</span> java.util.ArrayList;
<span class="hljs-keyword">import</span> java.util.HashMap;
<span class="hljs-keyword">import</span> java.util.List;
<span class="hljs-keyword">import</span> java.util.Map;

<span class="hljs-meta">@SuperBuilder</span>
<span class="hljs-meta">@ToString</span>
<span class="hljs-meta">@EqualsAndHashCode</span>
<span class="hljs-meta">@Getter</span>
<span class="hljs-meta">@NoArgsConstructor</span>
<span class="hljs-meta">@Plugin(
    examples = {
        @Example(
            title = "Query custom database",
            code = {
                "id: custom_query",
                "type: com.company.kestra.plugin.custom.CustomDatabaseQuery",
                "connectionString: jdbc:custom://host:port/db",
                "username: admin",
                "password: \"{{ secret('CUSTOM_DB_PASS') }}\"",
                "sql: SELECT * FROM users WHERE active = true"
            }
        )
    }
)</span>
<span class="hljs-meta">@Schema(title = "Custom Database Query", description = "Query a custom database system")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomDatabaseQuery</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Task</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">RunnableTask</span>&lt;<span class="hljs-title">CustomDatabaseQuery</span>.<span class="hljs-title">Output</span>&gt; </span>{

    <span class="hljs-meta">@NotNull</span>
    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "JDBC connection string")</span>
    <span class="hljs-keyword">private</span> String connectionString;

    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "Database username")</span>
    <span class="hljs-keyword">private</span> String username;

    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "Database password", secret = true)</span>
    <span class="hljs-keyword">private</span> String password;

    <span class="hljs-meta">@NotNull</span>
    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "SQL query to execute")</span>
    <span class="hljs-keyword">private</span> String sql;

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "Fetch results (true) or execute without results (false)")</span>
    <span class="hljs-meta">@Builder</span>.Default
    <span class="hljs-keyword">private</span> Boolean fetch = <span class="hljs-keyword">true</span>;

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "Fetch size for large results")</span>
    <span class="hljs-meta">@Builder</span>.Default
    <span class="hljs-keyword">private</span> Integer fetchSize = <span class="hljs-number">1000</span>;

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "Query timeout in seconds")</span>
    <span class="hljs-meta">@Builder</span>.Default
    <span class="hljs-keyword">private</span> Integer queryTimeout = <span class="hljs-number">30</span>;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Output <span class="hljs-title">run</span><span class="hljs-params">(RunContext runContext)</span> <span class="hljs-keyword">throws</span> Exception </span>{
        <span class="hljs-comment">// Render dynamic properties</span>
        String renderedConnectionString = runContext.render(connectionString);
        String renderedUsername = runContext.render(username);
        String renderedPassword = runContext.render(password);
        String renderedSql = runContext.render(sql);

        Connection connection = <span class="hljs-keyword">null</span>;
        PreparedStatement statement = <span class="hljs-keyword">null</span>;
        ResultSet resultSet = <span class="hljs-keyword">null</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// Load custom JDBC driver</span>
            Class.forName(<span class="hljs-string">"com.custom.Driver"</span>);

            <span class="hljs-comment">// Create connection</span>
            connection = DriverManager.getConnection(
                renderedConnectionString,
                renderedUsername,
                renderedPassword
            );

            <span class="hljs-comment">// Create statement</span>
            statement = connection.prepareStatement(renderedSql);
            statement.setFetchSize(fetchSize);
            statement.setQueryTimeout(queryTimeout);

            <span class="hljs-keyword">if</span> (fetch) {
                <span class="hljs-comment">// Execute query and fetch results</span>
                resultSet = statement.executeQuery();

                List&lt;Map&lt;String, Object&gt;&gt; rows = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();
                ResultSetMetaData metaData = resultSet.getMetaData();
                <span class="hljs-keyword">int</span> columnCount = metaData.getColumnCount();

                <span class="hljs-keyword">while</span> (resultSet.next()) {
                    Map&lt;String, Object&gt; row = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
                    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">1</span>; i &lt;= columnCount; i++) {
                        String columnName = metaData.getColumnName(i);
                        Object value = resultSet.getObject(i);
                        row.put(columnName, value);
                    }
                    rows.add(row);
                }

                <span class="hljs-keyword">return</span> Output.builder()
                    .rowCount(rows.size())
                    .rows(rows)
                    .query(renderedSql)
                    .build();

            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// Execute update</span>
                <span class="hljs-keyword">int</span> affectedRows = statement.executeUpdate();

                <span class="hljs-keyword">return</span> Output.builder()
                    .rowCount(affectedRows)
                    .query(renderedSql)
                    .build();
            }

        } <span class="hljs-keyword">finally</span> {
            <span class="hljs-comment">// Clean up resources</span>
            <span class="hljs-keyword">if</span> (resultSet != <span class="hljs-keyword">null</span>) {
                <span class="hljs-keyword">try</span> { resultSet.close(); } <span class="hljs-keyword">catch</span> (SQLException ignored) {}
            }
            <span class="hljs-keyword">if</span> (statement != <span class="hljs-keyword">null</span>) {
                <span class="hljs-keyword">try</span> { statement.close(); } <span class="hljs-keyword">catch</span> (SQLException ignored) {}
            }
            <span class="hljs-keyword">if</span> (connection != <span class="hljs-keyword">null</span>) {
                <span class="hljs-keyword">try</span> { connection.close(); } <span class="hljs-keyword">catch</span> (SQLException ignored) {}
            }
        }
    }

    <span class="hljs-meta">@Builder</span>
    <span class="hljs-meta">@Getter</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Output</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">io</span>.<span class="hljs-title">kestra</span>.<span class="hljs-title">core</span>.<span class="hljs-title">models</span>.<span class="hljs-title">tasks</span>.<span class="hljs-title">Output</span> </span>{
        <span class="hljs-meta">@Schema(title = "Number of rows affected or returned")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Integer rowCount;

        <span class="hljs-meta">@Schema(title = "Query results (only for SELECT queries)")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> List&lt;Map&lt;String, Object&gt;&gt; rows;

        <span class="hljs-meta">@Schema(title = "Executed SQL query")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String query;
    }
}
</code></pre>
<h3 id="heading-rest-api-plugin-example"><strong>REST API Plugin Example</strong></h3>
<pre><code class="lang-java"><span class="hljs-comment">// src/main/java/com/company/kestra/plugin/custom/CustomApiClient.java</span>
<span class="hljs-keyword">package</span> com.company.kestra.plugin.custom;

<span class="hljs-keyword">import</span> com.fasterxml.jackson.databind.ObjectMapper;
<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.Example;
<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.Plugin;
<span class="hljs-keyword">import</span> io.kestra.core.models.annotations.PluginProperty;
<span class="hljs-keyword">import</span> io.kestra.core.models.tasks.RunnableTask;
<span class="hljs-keyword">import</span> io.kestra.core.models.tasks.Task;
<span class="hljs-keyword">import</span> io.kestra.core.runners.RunContext;
<span class="hljs-keyword">import</span> io.kestra.core.serializers.JacksonMapper;
<span class="hljs-keyword">import</span> io.swagger.v3.oas.annotations.media.Schema;
<span class="hljs-keyword">import</span> lombok.*;
<span class="hljs-keyword">import</span> lombok.experimental.SuperBuilder;
<span class="hljs-keyword">import</span> okhttp3.*;

<span class="hljs-keyword">import</span> javax.validation.constraints.NotNull;
<span class="hljs-keyword">import</span> java.time.Duration;
<span class="hljs-keyword">import</span> java.util.HashMap;
<span class="hljs-keyword">import</span> java.util.Map;

<span class="hljs-meta">@SuperBuilder</span>
<span class="hljs-meta">@ToString</span>
<span class="hljs-meta">@EqualsAndHashCode</span>
<span class="hljs-meta">@Getter</span>
<span class="hljs-meta">@NoArgsConstructor</span>
<span class="hljs-meta">@Plugin(
    examples = {
        @Example(
            title = "Call custom API",
            code = {
                "id: call_custom_api",
                "type: com.company.kestra.plugin.custom.CustomApiClient",
                "baseUrl: https://api.company.com/v1",
                "endpoint: /users",
                "method: GET",
                "headers:",
                "  Authorization: Bearer {{ secret('API_TOKEN') }}",
                "queryParams:",
                "  status: active",
                "  limit: 100"
            }
        )
    }
)</span>
<span class="hljs-meta">@Schema(title = "Custom API Client", description = "Interact with a custom REST API")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomApiClient</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Task</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">RunnableTask</span>&lt;<span class="hljs-title">CustomApiClient</span>.<span class="hljs-title">Output</span>&gt; </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> ObjectMapper OBJECT_MAPPER = JacksonMapper.ofJson();
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> OkHttpClient HTTP_CLIENT = <span class="hljs-keyword">new</span> OkHttpClient.Builder()
        .connectTimeout(Duration.ofSeconds(<span class="hljs-number">30</span>))
        .readTimeout(Duration.ofSeconds(<span class="hljs-number">30</span>))
        .writeTimeout(Duration.ofSeconds(<span class="hljs-number">30</span>))
        .build();

    <span class="hljs-meta">@NotNull</span>
    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "API base URL")</span>
    <span class="hljs-keyword">private</span> String baseUrl;

    <span class="hljs-meta">@NotNull</span>
    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "API endpoint")</span>
    <span class="hljs-keyword">private</span> String endpoint;

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "HTTP method", defaultValue = "GET")</span>
    <span class="hljs-meta">@Builder</span>.Default
    <span class="hljs-keyword">private</span> Method method = Method.GET;

    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "Request headers")</span>
    <span class="hljs-keyword">private</span> Map&lt;String, String&gt; headers;

    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "Query parameters")</span>
    <span class="hljs-keyword">private</span> Map&lt;String, String&gt; queryParams;

    <span class="hljs-meta">@PluginProperty(dynamic = true)</span>
    <span class="hljs-meta">@Schema(title = "Request body")</span>
    <span class="hljs-keyword">private</span> Object body;

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "Timeout in seconds")</span>
    <span class="hljs-meta">@Builder</span>.Default
    <span class="hljs-keyword">private</span> Integer timeout = <span class="hljs-number">30</span>;

    <span class="hljs-meta">@PluginProperty</span>
    <span class="hljs-meta">@Schema(title = "Store response as file")</span>
    <span class="hljs-meta">@Builder</span>.Default
    <span class="hljs-keyword">private</span> Boolean store = <span class="hljs-keyword">false</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Method</span> </span>{
        GET, POST, PUT, DELETE, PATCH
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Output <span class="hljs-title">run</span><span class="hljs-params">(RunContext runContext)</span> <span class="hljs-keyword">throws</span> Exception </span>{
        <span class="hljs-comment">// Render dynamic properties</span>
        String renderedBaseUrl = runContext.render(baseUrl);
        String renderedEndpoint = runContext.render(endpoint);
        Map&lt;String, String&gt; renderedHeaders = renderMap(runContext, headers);
        Map&lt;String, String&gt; renderedQueryParams = renderMap(runContext, queryParams);
        Object renderedBody = renderBody(runContext, body);

        <span class="hljs-comment">// Build URL with query parameters</span>
        HttpUrl.Builder urlBuilder = HttpUrl.parse(renderedBaseUrl + renderedEndpoint).newBuilder();
        <span class="hljs-keyword">if</span> (renderedQueryParams != <span class="hljs-keyword">null</span>) {
            renderedQueryParams.forEach(urlBuilder::addQueryParameter);
        }

        <span class="hljs-comment">// Build request</span>
        Request.Builder requestBuilder = <span class="hljs-keyword">new</span> Request.Builder()
            .url(urlBuilder.build());

        <span class="hljs-comment">// Add headers</span>
        <span class="hljs-keyword">if</span> (renderedHeaders != <span class="hljs-keyword">null</span>) {
            renderedHeaders.forEach(requestBuilder::addHeader);
        }

        <span class="hljs-comment">// Add body for methods that support it</span>
        <span class="hljs-keyword">if</span> (renderedBody != <span class="hljs-keyword">null</span> &amp;&amp; 
            (method == Method.POST || method == Method.PUT || 
             method == Method.PATCH || method == Method.DELETE)) {

            String bodyString;
            <span class="hljs-keyword">if</span> (renderedBody <span class="hljs-keyword">instanceof</span> String) {
                bodyString = (String) renderedBody;
                requestBuilder.addHeader(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"text/plain"</span>);
            } <span class="hljs-keyword">else</span> {
                bodyString = OBJECT_MAPPER.writeValueAsString(renderedBody);
                requestBuilder.addHeader(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>);
            }

            RequestBody requestBody = RequestBody.create(
                bodyString, 
                MediaType.parse(requestBuilder.build().header(<span class="hljs-string">"Content-Type"</span>))
            );

            requestBuilder.method(method.name(), requestBody);
        } <span class="hljs-keyword">else</span> {
            requestBuilder.method(method.name(), <span class="hljs-keyword">null</span>);
        }

        <span class="hljs-comment">// Execute request with custom timeout</span>
        OkHttpClient client = HTTP_CLIENT.newBuilder()
            .connectTimeout(Duration.ofSeconds(timeout))
            .readTimeout(Duration.ofSeconds(timeout))
            .writeTimeout(Duration.ofSeconds(timeout))
            .build();

        <span class="hljs-keyword">try</span> (Response response = client.newCall(requestBuilder.build()).execute()) {
            String responseBody = response.body() != <span class="hljs-keyword">null</span> ? 
                response.body().string() : <span class="hljs-keyword">null</span>;

            Map&lt;String, Object&gt; responseMap = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
            <span class="hljs-keyword">if</span> (responseBody != <span class="hljs-keyword">null</span> &amp;&amp; !responseBody.isEmpty()) {
                <span class="hljs-keyword">try</span> {
                    responseMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
                } <span class="hljs-keyword">catch</span> (Exception e) {
                    responseMap.put("raw", responseBody);
                }
            }

            <span class="hljs-keyword">return</span> Output.builder()
                .statusCode(response.code())
                .headers(response.headers().toMultimap())
                .body(responseMap)
                .success(response.isSuccessful())
                .message(response.message())
                .build();
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Map&lt;String, String&gt; <span class="hljs-title">renderMap</span><span class="hljs-params">(RunContext runContext, Map&lt;String, String&gt; map)</span> </span>{
        <span class="hljs-keyword">if</span> (map == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;

        Map&lt;String, String&gt; rendered = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
        map.forEach((key, value) -&gt; {
            rendered.put(key, runContext.render(value));
        });
        <span class="hljs-keyword">return</span> rendered;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Object <span class="hljs-title">renderBody</span><span class="hljs-params">(RunContext runContext, Object body)</span> </span>{
        <span class="hljs-keyword">if</span> (body == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;

        <span class="hljs-keyword">if</span> (body <span class="hljs-keyword">instanceof</span> String) {
            <span class="hljs-keyword">return</span> runContext.render((String) body);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (body <span class="hljs-keyword">instanceof</span> Map) {
            <span class="hljs-comment">// Deep render map values</span>
            Map&lt;?, ?&gt; map = (Map&lt;?, ?&gt;) body;
            Map&lt;Object, Object&gt; rendered = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
            map.forEach((key, value) -&gt; {
                Object renderedKey = key <span class="hljs-keyword">instanceof</span> String ? 
                    runContext.render((String) key) : key;
                Object renderedValue = value <span class="hljs-keyword">instanceof</span> String ? 
                    runContext.render((String) value) : value;
                rendered.put(renderedKey, renderedValue);
            });
            <span class="hljs-keyword">return</span> rendered;
        }
        <span class="hljs-keyword">return</span> body;
    }

    <span class="hljs-meta">@Builder</span>
    <span class="hljs-meta">@Getter</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Output</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">io</span>.<span class="hljs-title">kestra</span>.<span class="hljs-title">core</span>.<span class="hljs-title">models</span>.<span class="hljs-title">tasks</span>.<span class="hljs-title">Output</span> </span>{
        <span class="hljs-meta">@Schema(title = "HTTP status code")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Integer statusCode;

        <span class="hljs-meta">@Schema(title = "Response headers")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;String, java.util.List&lt;String&gt;&gt; headers;

        <span class="hljs-meta">@Schema(title = "Response body")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;String, Object&gt; body;

        <span class="hljs-meta">@Schema(title = "Whether the request was successful")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Boolean success;

        <span class="hljs-meta">@Schema(title = "Response message")</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String message;
    }
}
</code></pre>
<h3 id="heading-plugin-configuration-pomxml"><strong>Plugin Configuration (pom.xml)</strong></h3>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- pom.xml for custom plugin --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.company.kestra<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>kestra-plugin-custom<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">packaging</span>&gt;</span>jar<span class="hljs-tag">&lt;/<span class="hljs-name">packaging</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>Kestra Custom Plugin<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Custom plugins for Company's systems<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">kestra.version</span>&gt;</span>0.15.0<span class="hljs-tag">&lt;/<span class="hljs-name">kestra.version</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.source</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.source</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.target</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.target</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">project.build.sourceEncoding</span>&gt;</span>UTF-8<span class="hljs-tag">&lt;/<span class="hljs-name">project.build.sourceEncoding</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- Kestra Core --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.kestra<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>core<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>${kestra.version}<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>provided<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- External Dependencies --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.squareup.okhttp3<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>okhttp<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>4.11.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.fasterxml.jackson.core<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>jackson-databind<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.15.2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- Test Dependencies --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.kestra<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>junit5<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>${kestra.version}<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.11.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-shade-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.4.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">executions</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">phase</span>&gt;</span>package<span class="hljs-tag">&lt;/<span class="hljs-name">phase</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>shade<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">createDependencyReducedPom</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">createDependencyReducedPom</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">filters</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">filter</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">artifact</span>&gt;</span>*:*<span class="hljs-tag">&lt;/<span class="hljs-name">artifact</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">excludes</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">exclude</span>&gt;</span>META-INF/*.SF<span class="hljs-tag">&lt;/<span class="hljs-name">exclude</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">exclude</span>&gt;</span>META-INF/*.DSA<span class="hljs-tag">&lt;/<span class="hljs-name">exclude</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">exclude</span>&gt;</span>META-INF/*.RSA<span class="hljs-tag">&lt;/<span class="hljs-name">exclude</span>&gt;</span>
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">excludes</span>&gt;</span>
                                <span class="hljs-tag">&lt;/<span class="hljs-name">filter</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">filters</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">executions</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<h3 id="heading-testing-custom-plugins"><strong>Testing Custom Plugins</strong></h3>
<pre><code class="lang-java"><span class="hljs-comment">// src/test/java/com/company/kestra/plugin/custom/CustomApiClientTest.java</span>
<span class="hljs-keyword">package</span> com.company.kestra.plugin.custom;

<span class="hljs-keyword">import</span> io.kestra.core.runners.RunContextFactory;
<span class="hljs-keyword">import</span> io.kestra.core.utils.TestsUtils;
<span class="hljs-keyword">import</span> io.micronaut.test.extensions.junit5.annotation.MicronautTest;
<span class="hljs-keyword">import</span> jakarta.inject.Inject;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test;

<span class="hljs-keyword">import</span> java.util.HashMap;
<span class="hljs-keyword">import</span> java.util.Map;

<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> org.hamcrest.MatcherAssert.assertThat;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> org.hamcrest.Matchers.*;

<span class="hljs-meta">@MicronautTest</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomApiClientTest</span> </span>{

    <span class="hljs-meta">@Inject</span>
    <span class="hljs-keyword">private</span> RunContextFactory runContextFactory;

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">testApiCall</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        <span class="hljs-comment">// Create task instance</span>
        CustomApiClient task = CustomApiClient.builder()
            .id(<span class="hljs-string">"test-api"</span>)
            .type(CustomApiClient.class.getName())
            .baseUrl("https://httpbin.org")
            .endpoint("/get")
            .method(CustomApiClient.Method.GET)
            .headers(Map.of(
                "User-Agent", "Kestra-Plugin-Test"
            ))
            .queryParams(Map.of(
                "test", "value"
            ))
            .build();

        // Execute task
        CustomApiClient.Output output = task.run(
            TestsUtils.mockRunContext(
                runContextFactory, 
                task, 
                <span class="hljs-keyword">new</span> HashMap&lt;&gt;()
            )
        );

        <span class="hljs-comment">// Verify results</span>
        assertThat(output.getStatusCode(), is(<span class="hljs-number">200</span>));
        assertThat(output.getSuccess(), is(<span class="hljs-keyword">true</span>));
        assertThat(output.getBody(), notNullValue());

        <span class="hljs-comment">// Verify response contains our query param</span>
        Map&lt;String, Object&gt; args = (Map&lt;String, Object&gt;) 
            ((Map&lt;String, Object&gt;) output.getBody().get(<span class="hljs-string">"args"</span>));
        assertThat(args.get(<span class="hljs-string">"test"</span>), is(<span class="hljs-string">"value"</span>));
    }

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">testPostRequest</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        Map&lt;String, Object&gt; requestBody = Map.of(
            <span class="hljs-string">"name"</span>, <span class="hljs-string">"John Doe"</span>,
            <span class="hljs-string">"email"</span>, <span class="hljs-string">"john@example.com"</span>,
            <span class="hljs-string">"active"</span>, <span class="hljs-keyword">true</span>
        );

        CustomApiClient task = CustomApiClient.builder()
            .id(<span class="hljs-string">"test-post"</span>)
            .type(CustomApiClient.class.getName())
            .baseUrl("https://httpbin.org")
            .endpoint("/post")
            .method(CustomApiClient.Method.POST)
            .body(requestBody)
            .build();

        CustomApiClient.Output output = task.run(
            TestsUtils.mockRunContext(
                runContextFactory, 
                task, 
                <span class="hljs-keyword">new</span> HashMap&lt;&gt;()
            )
        );

        assertThat(output.getStatusCode(), is(<span class="hljs-number">200</span>));

        Map&lt;String, Object&gt; json = (Map&lt;String, Object&gt;) 
            output.getBody().get(<span class="hljs-string">"json"</span>);
        assertThat(json.get(<span class="hljs-string">"name"</span>), is(<span class="hljs-string">"John Doe"</span>));
        assertThat(json.get(<span class="hljs-string">"active"</span>), is(<span class="hljs-keyword">true</span>));
    }
}
</code></pre>
<h3 id="heading-plugin-documentation"><strong>Plugin Documentation</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Custom Database Plugin</span>

<span class="hljs-comment">## Overview</span>
This plugin allows querying custom database systems from Kestra workflows.

<span class="hljs-comment">## Installation</span>
1. Build the plugin:
   ```bash
   mvn clean package
</code></pre>
<ol start="2">
<li><p>Copy the JAR to Kestra's plugins directory:</p>
<p> bash</p>
<pre><code class="lang-bash"> cp target/kestra-plugin-custom-1.0.0.jar /opt/kestra/plugins/
</code></pre>
</li>
<li><p>Restart Kestra.</p>
</li>
</ol>
<h2 id="heading-usage">Usage</h2>
<h3 id="heading-basic-query">Basic Query</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">query_custom_db</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">com.company.kestra.plugin.custom.CustomDatabaseQuery</span>
<span class="hljs-attr">connectionString:</span> <span class="hljs-string">jdbc:custom://host:5432/database</span>
<span class="hljs-attr">username:</span> <span class="hljs-string">admin</span>
<span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('DB_PASSWORD') }}</span>"</span>
<span class="hljs-attr">sql:</span> <span class="hljs-string">|
  SELECT * FROM users 
  WHERE created_at &gt; '2024-01-01'
</span><span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
</code></pre>
<h3 id="heading-update-query">Update Query</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">update_records</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">com.company.kestra.plugin.custom.CustomDatabaseQuery</span>
<span class="hljs-attr">connectionString:</span> <span class="hljs-string">jdbc:custom://host:5432/database</span>
<span class="hljs-attr">sql:</span> <span class="hljs-string">|
  UPDATE users 
  SET status = 'inactive'
  WHERE last_login &lt; '2024-01-01'
</span><span class="hljs-attr">fetch:</span> <span class="hljs-literal">false</span>
</code></pre>
<h2 id="heading-properties">Properties</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Property</td><td>Type</td><td>Required</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>connectionString</td><td>String</td><td>Yes</td><td>JDBC connection string</td></tr>
<tr>
<td>username</td><td>String</td><td>No</td><td>Database username</td></tr>
<tr>
<td>password</td><td>String</td><td>No</td><td>Database password</td></tr>
<tr>
<td>sql</td><td>String</td><td>Yes</td><td>SQL query to execute</td></tr>
<tr>
<td>fetch</td><td>Boolean</td><td>No</td><td>Whether to fetch results (default: true)</td></tr>
<tr>
<td>fetchSize</td><td>Integer</td><td>No</td><td>Fetch size for large results (default: 1000)</td></tr>
<tr>
<td>queryTimeout</td><td>Integer</td><td>No</td><td>Query timeout in seconds (default: 30)</td></tr>
</tbody>
</table>
</div><h2 id="heading-outputs">Outputs</h2>
<h3 id="heading-for-select-queries-fetch-true">For SELECT queries (fetch: true):</h3>
<pre><code class="lang-plaintext">{
  "rowCount": 100,
  "rows": [...],
  "query": "SELECT * FROM users"
}
</code></pre>
<h3 id="heading-for-updateinsertdelete-queries-fetch-false">For UPDATE/INSERT/DELETE queries (fetch: false):</h3>
<pre><code class="lang-plaintext">{
  "rowCount": 50,
  "query": "UPDATE users SET status = 'active'"
}
</code></pre>
<h2 id="heading-examples">Examples</h2>
<p>See the <a target="_blank" href="https://examples/">examples directory</a> for more usage scenarios.</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>

<span class="hljs-comment">## **Part 4: Plugin Management and Optimization**</span>

<span class="hljs-comment">### **Plugin Registry Configuration**</span>

<span class="hljs-string">```yaml</span>
<span class="hljs-comment"># kestra.yml - Plugin configuration</span>
<span class="hljs-attr">kestra:</span>
  <span class="hljs-attr">plugins:</span>
    <span class="hljs-comment"># Plugin repositories</span>
    <span class="hljs-attr">repositories:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">kestra-io</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">https://repo.kestra.io/repository/maven-public/</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">maven</span>
        <span class="hljs-attr">releases:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">snapshots:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">company-private</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">https://nexus.company.com/repository/maven-releases/</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">maven</span>
        <span class="hljs-attr">releases:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">snapshots:</span> <span class="hljs-literal">false</span>
        <span class="hljs-attr">authentication:</span>
          <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('NEXUS_USER') }}</span>"</span>
          <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('NEXUS_PASS') }}</span>"</span>

    <span class="hljs-comment"># Plugin locations</span>
    <span class="hljs-attr">locations:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/opt/kestra/plugins</span>  <span class="hljs-comment"># System plugins</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/app/plugins</span>         <span class="hljs-comment"># User plugins</span>

    <span class="hljs-comment"># Plugin scanning</span>
    <span class="hljs-attr">scan:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">PT5M</span>  <span class="hljs-comment"># Scan every 5 minutes</span>

    <span class="hljs-comment"># Plugin isolation</span>
    <span class="hljs-attr">isolation:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">mode:</span> <span class="hljs-string">PROCESS</span>  <span class="hljs-comment"># PROCESS or THREAD</span>
      <span class="hljs-attr">process:</span>
        <span class="hljs-attr">maxThreads:</span> <span class="hljs-number">10</span>
        <span class="hljs-attr">maxMemory:</span> <span class="hljs-string">1Gi</span>
        <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT5M</span>

    <span class="hljs-comment"># Cache configuration</span>
    <span class="hljs-attr">cache:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">size:</span> <span class="hljs-number">1000</span>
      <span class="hljs-attr">expireAfterWrite:</span> <span class="hljs-string">PT1H</span>
</code></pre>
<h3 id="heading-plugin-version-management"><strong>Plugin Version Management</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/plugin-management.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">plugin-version-manager</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">system.plugins</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_plugin_updates</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://repo.kestra.io/repository/maven-public/io/kestra/plugin/maven-metadata.xml"</span>
    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">parse_plugin_versions</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">metadata.xml:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.check_plugin_updates.uri }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import xml.etree.ElementTree as ET
      import json
</span>
      <span class="hljs-string">tree</span> <span class="hljs-string">=</span> <span class="hljs-string">ET.parse('metadata.xml')</span>
      <span class="hljs-string">root</span> <span class="hljs-string">=</span> <span class="hljs-string">tree.getroot()</span>

      <span class="hljs-string">versions</span> <span class="hljs-string">=</span> []
      <span class="hljs-string">for</span> <span class="hljs-string">version</span> <span class="hljs-string">in</span> <span class="hljs-string">root.find('.//versioning/versions'):</span>
          <span class="hljs-string">versions.append(version.text)</span>

      <span class="hljs-string">latest</span> <span class="hljs-string">=</span> <span class="hljs-string">root.find('.//versioning/latest').text</span>
      <span class="hljs-string">release</span> <span class="hljs-string">=</span> <span class="hljs-string">root.find('.//versioning/release').text</span>

      <span class="hljs-string">result</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'total_versions':</span> <span class="hljs-string">len(versions)</span>,
          <span class="hljs-attr">'latest_version':</span> <span class="hljs-string">latest</span>,
          <span class="hljs-attr">'release_version':</span> <span class="hljs-string">release</span>,
          <span class="hljs-attr">'all_versions':</span> <span class="hljs-string">versions</span>[<span class="hljs-number">-10</span><span class="hljs-string">:</span>]  <span class="hljs-comment"># Last 10 versions</span>
      }

      <span class="hljs-string">print(json.dumps(result,</span> <span class="hljs-string">indent=2))</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_plugins_if_needed</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
    <span class="hljs-attr">value:</span> [<span class="hljs-string">"postgresql"</span>, <span class="hljs-string">"s3"</span>, <span class="hljs-string">"snowflake"</span>, <span class="hljs-string">"bigquery"</span>]
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"check_<span class="hljs-template-variable">{{ task.value }}</span>_version"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value }}</span>: 1.2.3"</span>  <span class="hljs-comment"># Would be actual version check</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"update_<span class="hljs-template-variable">{{ task.value }}</span>_if_outdated"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">system.maintenance</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">update-plugin</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">plugin_name</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"kestra-plugin-jdbc-<span class="hljs-template-variable">{{ task.value }}</span>"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">target_version</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"latest"</span>
        <span class="hljs-attr">conditions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
            <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ true }}</span>"</span>  <span class="hljs-comment"># Actual version comparison</span>
</code></pre>
<h3 id="heading-plugin-performance-optimization"><strong>Plugin Performance Optimization</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/plugin-performance.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">plugin-performance-optimization</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">demo.performance</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Batch operations instead of individual calls</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">batch_database_operations</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      -- Batch insert
      INSERT INTO users (id, name, email) VALUES
      {% for user in inputs.users %}
      ('{{ user.id }}', '{{ user.name }}', '{{ user.email }}'){{ ',' if not loop.last }}
      {% endfor %}
</span>
      <span class="hljs-string">ON</span> <span class="hljs-string">CONFLICT</span> <span class="hljs-string">(id)</span> <span class="hljs-string">DO</span> <span class="hljs-string">UPDATE</span> <span class="hljs-string">SET</span>
        <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">EXCLUDED.name,</span>
        <span class="hljs-string">email</span> <span class="hljs-string">=</span> <span class="hljs-string">EXCLUDED.email;</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">false</span>

  <span class="hljs-comment"># Connection pooling</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">connection_pool_config</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:postgresql://localhost:5432/db</span>
    <span class="hljs-attr">username:</span> <span class="hljs-string">user</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">pass</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">SELECT</span> <span class="hljs-string">*</span> <span class="hljs-string">FROM</span> <span class="hljs-string">large_table</span>
    <span class="hljs-attr">connectionProperties:</span>
      <span class="hljs-attr">maximumPoolSize:</span> <span class="hljs-string">"10"</span>
      <span class="hljs-attr">minimumIdle:</span> <span class="hljs-string">"5"</span>
      <span class="hljs-attr">connectionTimeout:</span> <span class="hljs-string">"30000"</span>
      <span class="hljs-attr">idleTimeout:</span> <span class="hljs-string">"600000"</span>
      <span class="hljs-attr">maxLifetime:</span> <span class="hljs-string">"1800000"</span>

  <span class="hljs-comment"># Parallel plugin execution</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">parallel_plugin_execution</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">api_call_1</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.example.com/data1"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">api_call_2</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.example.com/data2"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">api_call_3</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.example.com/data3"</span>

  <span class="hljs-comment"># Plugin caching</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cached_operation</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Cached</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"expensive_operation:<span class="hljs-template-variable">{{ inputs.user_id }}</span>"</span>
    <span class="hljs-attr">ttl:</span> <span class="hljs-string">PT1H</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">expensive_computation</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
        <span class="hljs-attr">script:</span> <span class="hljs-string">|
          import time
          import json
</span>
          <span class="hljs-comment"># Simulate expensive computation</span>
          <span class="hljs-string">time.sleep(5)</span>

          <span class="hljs-string">result</span> <span class="hljs-string">=</span> {<span class="hljs-attr">"computed":</span> <span class="hljs-literal">True</span>, <span class="hljs-attr">"value":</span> <span class="hljs-number">42</span>}
          <span class="hljs-string">print(json.dumps(result))</span>

  <span class="hljs-comment"># Memory optimization for large data</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_large_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">SELECT</span> <span class="hljs-string">*</span> <span class="hljs-string">FROM</span> <span class="hljs-string">very_large_table</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">fetchSize:</span> <span class="hljs-number">10000</span>  <span class="hljs-comment"># Process in chunks</span>
    <span class="hljs-attr">chunkSize:</span> <span class="hljs-number">1000</span>   <span class="hljs-comment"># Smaller chunks for memory efficiency</span>
</code></pre>
<h3 id="heading-plugin-monitoring-and-metrics"><strong>Plugin Monitoring and Metrics</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/plugin-monitoring.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">plugin-monitoring-dashboard</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">system.monitoring</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">collect_plugin_metrics</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      WITH plugin_stats AS (
        SELECT 
          SUBSTRING(task_type FROM 'plugin\.([^.]*)') as plugin_name,
          COUNT(*) as execution_count,
          AVG(EXTRACT(EPOCH FROM (end_date - start_date))) * 1000 as avg_duration_ms,
          PERCENTILE_CONT(0.95) WITHIN GROUP (
            ORDER BY EXTRACT(EPOCH FROM (end_date - start_date)) * 1000
          ) as p95_duration_ms,
          SUM(CASE WHEN state = 'FAILED' THEN 1 ELSE 0 END) as failure_count
        FROM executions
        WHERE start_date &gt;= NOW() - INTERVAL '7 days'
          AND task_type LIKE 'io.kestra.plugin.%'
        GROUP BY plugin_name
      ),
      plugin_health AS (
        SELECT 
          plugin_name,
          execution_count,
          avg_duration_ms,
          p95_duration_ms,
          failure_count,
          CASE 
            WHEN failure_count = 0 THEN 'HEALTHY'
            WHEN failure_count::float / execution_count &lt; 0.01 THEN 'WARNING'
            ELSE 'CRITICAL'
          END as health_status
        FROM plugin_stats
      )
      SELECT * FROM plugin_health
      ORDER BY execution_count DESC
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_plugin_report</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">metrics.json:</span> <span class="hljs-string">|
        {{ outputs.collect_plugin_metrics.rows | tojson }}
</span>    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
      import pandas as pd
      from datetime import datetime
</span>
      <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">json.loads('metrics.json')</span>
      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(data)</span>

      <span class="hljs-comment"># Create summary report</span>
      <span class="hljs-string">report</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'generated_at':</span> <span class="hljs-string">datetime.now().isoformat()</span>,
          <span class="hljs-attr">'total_plugins':</span> <span class="hljs-string">len(df)</span>,
          <span class="hljs-attr">'total_executions':</span> <span class="hljs-string">df</span>[<span class="hljs-string">'execution_count'</span>]<span class="hljs-string">.sum()</span>,
          <span class="hljs-attr">'health_summary':</span> {
              <span class="hljs-attr">'healthy':</span> <span class="hljs-string">len(df</span>[<span class="hljs-string">df</span>[<span class="hljs-string">'health_status'</span>] <span class="hljs-string">==</span> <span class="hljs-string">'HEALTHY'</span>]<span class="hljs-string">)</span>,
              <span class="hljs-attr">'warning':</span> <span class="hljs-string">len(df</span>[<span class="hljs-string">df</span>[<span class="hljs-string">'health_status'</span>] <span class="hljs-string">==</span> <span class="hljs-string">'WARNING'</span>]<span class="hljs-string">)</span>,
              <span class="hljs-attr">'critical':</span> <span class="hljs-string">len(df</span>[<span class="hljs-string">df</span>[<span class="hljs-string">'health_status'</span>] <span class="hljs-string">==</span> <span class="hljs-string">'CRITICAL'</span>]<span class="hljs-string">)</span>
          },
          <span class="hljs-attr">'top_plugins_by_usage':</span> <span class="hljs-string">df.nlargest(10</span>, <span class="hljs-string">'execution_count'</span><span class="hljs-string">).to_dict('records')</span>,
          <span class="hljs-attr">'slowest_plugins':</span> <span class="hljs-string">df.nlargest(5</span>, <span class="hljs-string">'p95_duration_ms'</span><span class="hljs-string">).to_dict('records')</span>,
          <span class="hljs-attr">'most_failing_plugins':</span> <span class="hljs-string">df.nlargest(5</span>, <span class="hljs-string">'failure_count'</span><span class="hljs-string">).to_dict('records')</span>
      }

      <span class="hljs-string">print(json.dumps(report,</span> <span class="hljs-string">indent=2))</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">alert_on_plugin_issues</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_WEBHOOK_URL') }}</span>"</span>
    <span class="hljs-attr">payload:</span> <span class="hljs-string">|
      {
        "blocks": [
          {
            "type": "header",
            "text": {
              "type": "plain_text",
              "text": "⚠️ Plugin Health Alert"
            }
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Critical Plugins:*\n{% for plugin in outputs.generate_plugin_report.vars.most_failing_plugins %}- {{ plugin.plugin_name }}: {{ plugin.failure_count }} failures\n{% endfor %}"
            }
          }
        ]
      }
</span>    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.generate_plugin_report.vars.health_summary.critical &gt; 0 }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-part-5-enterprise-plugin-patterns"><strong>Part 5: Enterprise Plugin Patterns</strong></h2>
<h3 id="heading-plugin-factory-pattern"><strong>Plugin Factory Pattern</strong></h3>
<pre><code class="lang-java"><span class="hljs-comment">// Plugin factory for creating plugin instances dynamically</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PluginFactory</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;String, PluginCreator&gt; creators = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PluginFactory</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-comment">// Register plugin creators</span>
        creators.put(<span class="hljs-string">"salesforce"</span>, <span class="hljs-keyword">new</span> SalesforcePluginCreator());
        creators.put(<span class="hljs-string">"sap"</span>, <span class="hljs-keyword">new</span> SapPluginCreator());
        creators.put(<span class="hljs-string">"oracle"</span>, <span class="hljs-keyword">new</span> OraclePluginCreator());
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">createPlugin</span><span class="hljs-params">(String system, Map&lt;String, Object&gt; config)</span> </span>{
        PluginCreator creator = creators.get(system.toLowerCase());
        <span class="hljs-keyword">if</span> (creator == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Unknown system: "</span> + system);
        }
        <span class="hljs-keyword">return</span> creator.create(config);
    }

    <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">PluginCreator</span> </span>{
        <span class="hljs-function">Task <span class="hljs-title">create</span><span class="hljs-params">(Map&lt;String, Object&gt; config)</span></span>;
    }

    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SalesforcePluginCreator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">PluginCreator</span> </span>{
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">create</span><span class="hljs-params">(Map&lt;String, Object&gt; config)</span> </span>{
            <span class="hljs-keyword">return</span> SalesforceQuery.builder()
                .instanceUrl((String) config.get(<span class="hljs-string">"instanceUrl"</span>))
                .clientId((String) config.get(<span class="hljs-string">"clientId"</span>))
                .clientSecret((String) config.get(<span class="hljs-string">"clientSecret"</span>))
                .soql((String) config.get(<span class="hljs-string">"query"</span>))
                .build();
        }
    }
}
</code></pre>
<h3 id="heading-plugin-adapter-pattern"><strong>Plugin Adapter Pattern</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/plugin-adapter.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">legacy-system-adapter</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">enterprise.integration</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Adapter for legacy SOAP service</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">soap_to_rest_adapter</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import zeep
      import requests
      import json
</span>
      <span class="hljs-comment"># Legacy SOAP client</span>
      <span class="hljs-string">client</span> <span class="hljs-string">=</span> <span class="hljs-string">zeep.Client('https://legacy.company.com/wsdl')</span>

      <span class="hljs-comment"># Convert REST request to SOAP</span>
      <span class="hljs-string">request_data</span> <span class="hljs-string">=</span> {{ <span class="hljs-string">inputs.rest_request</span> <span class="hljs-string">|</span> <span class="hljs-string">tojson</span> }}

      <span class="hljs-string">soap_response</span> <span class="hljs-string">=</span> <span class="hljs-string">client.service.ProcessRequest(</span>
          <span class="hljs-string">method=request_data['method'],</span>
          <span class="hljs-string">params=json.dumps(request_data['params'])</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Convert SOAP response to REST format</span>
      <span class="hljs-string">rest_response</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'status':</span> <span class="hljs-string">'success'</span> <span class="hljs-string">if</span> <span class="hljs-string">soap_response.success</span> <span class="hljs-string">else</span> <span class="hljs-string">'error'</span>,
          <span class="hljs-attr">'data':</span> <span class="hljs-string">json.loads(soap_response.data)</span>,
          <span class="hljs-attr">'timestamp':</span> <span class="hljs-string">soap_response.timestamp</span>
      }

      <span class="hljs-string">print(json.dumps(rest_response))</span>

  <span class="hljs-comment"># Adapter for mainframe (via FTP)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">mainframe_adapter</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.fs.sftp.Download</span>
    <span class="hljs-attr">host:</span> <span class="hljs-string">mainframe.company.com</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">22</span>
    <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('MAINFRAME_USER') }}</span>"</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('MAINFRAME_PASS') }}</span>"</span>
    <span class="hljs-attr">from:</span> <span class="hljs-string">"/reports/<span class="hljs-template-variable">{{ execution.startDate | date('yyyyMMdd') }}</span>.txt"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">parse_mainframe_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">mainframe_data.txt:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.mainframe_adapter.uri }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      # Parse fixed-width mainframe format
      import struct
      import pandas as pd
</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('mainframe_data.txt',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">lines</span> <span class="hljs-string">=</span> <span class="hljs-string">f.readlines()</span>

      <span class="hljs-comment"># Define fixed-width format (example)</span>
      <span class="hljs-string">format_spec</span> <span class="hljs-string">=</span> [
          <span class="hljs-string">('customer_id'</span>, <span class="hljs-number">10</span><span class="hljs-string">)</span>,
          <span class="hljs-string">('name'</span>, <span class="hljs-number">30</span><span class="hljs-string">)</span>,
          <span class="hljs-string">('balance'</span>, <span class="hljs-number">12</span><span class="hljs-string">)</span>,
          <span class="hljs-string">('status'</span>, <span class="hljs-number">1</span><span class="hljs-string">)</span>
      ]

      <span class="hljs-string">records</span> <span class="hljs-string">=</span> []
      <span class="hljs-attr">for line in lines:</span>
          <span class="hljs-string">record</span> <span class="hljs-string">=</span> {}
          <span class="hljs-string">pos</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
          <span class="hljs-string">for</span> <span class="hljs-string">field,</span> <span class="hljs-attr">width in format_spec:</span>
              <span class="hljs-string">value</span> <span class="hljs-string">=</span> <span class="hljs-string">line[pos:pos+width].strip()</span>
              <span class="hljs-string">record[field]</span> <span class="hljs-string">=</span> <span class="hljs-string">value</span>
              <span class="hljs-string">pos</span> <span class="hljs-string">+=</span> <span class="hljs-string">width</span>
          <span class="hljs-string">records.append(record)</span>

      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(records)</span>
      <span class="hljs-string">df.to_parquet('mainframe_parsed.parquet',</span> <span class="hljs-string">index=False)</span>
</code></pre>
<h3 id="heading-plugin-security-patterns"><strong>Plugin Security Patterns</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/secure-plugin-usage.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">secure-plugin-patterns</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">enterprise.security</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Secrets management</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">secure_database_connection</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"jdbc:postgresql://<span class="hljs-template-variable">{{ secret('DB_HOST') }}</span>:5432/<span class="hljs-template-variable">{{ secret('DB_NAME') }}</span>"</span>
    <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('DB_USER') }}</span>"</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('DB_PASSWORD') }}</span>"</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">SELECT</span> <span class="hljs-string">*</span> <span class="hljs-string">FROM</span> <span class="hljs-string">secure_table</span>
    <span class="hljs-attr">connectionProperties:</span>
      <span class="hljs-attr">ssl:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">sslmode:</span> <span class="hljs-string">"verify-full"</span>
      <span class="hljs-attr">sslrootcert:</span> <span class="hljs-string">"/certs/ca.pem"</span>

  <span class="hljs-comment"># Encrypted data handling</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_encrypted_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import gnupg
      import pandas as pd
      from io import StringIO
</span>
      <span class="hljs-comment"># Initialize GPG</span>
      <span class="hljs-string">gpg</span> <span class="hljs-string">=</span> <span class="hljs-string">gnupg.GPG(gnupghome='/app/.gnupg')</span>

      <span class="hljs-comment"># Decrypt data</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('encrypted_data.pgp',</span> <span class="hljs-string">'rb'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">decrypted</span> <span class="hljs-string">=</span> <span class="hljs-string">gpg.decrypt_file(f,</span> <span class="hljs-string">passphrase='{{</span> <span class="hljs-string">secret('GPG_PASSPHRASE')</span> <span class="hljs-string">}}')</span>

      <span class="hljs-attr">if decrypted.ok:</span>
          <span class="hljs-comment"># Process decrypted data</span>
          <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_csv(StringIO(str(decrypted)))</span>
          <span class="hljs-comment"># ... processing ...</span>

          <span class="hljs-comment"># Re-encrypt results</span>
          <span class="hljs-string">encrypted</span> <span class="hljs-string">=</span> <span class="hljs-string">gpg.encrypt(</span>
              <span class="hljs-string">df.to_csv(index=False),</span>
              <span class="hljs-string">recipients=['data-team@company.com'],</span>
              <span class="hljs-string">always_trust=True</span>
          <span class="hljs-string">)</span>

          <span class="hljs-string">with</span> <span class="hljs-string">open('results.pgp',</span> <span class="hljs-string">'w'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
              <span class="hljs-string">f.write(str(encrypted))</span>
      <span class="hljs-attr">else:</span>
          <span class="hljs-string">raise</span> <span class="hljs-string">Exception(f"Decryption</span> <span class="hljs-attr">failed:</span> {<span class="hljs-string">decrypted.stderr</span>}<span class="hljs-string">")
    docker:
      image: python:3.11-slim
      volumes:
        - /secrets/gnupg:/app/.gnupg:ro

  # Audit logging for sensitive operations
  - id: audited_plugin_operation
    type: io.kestra.plugin.core.flow.Sequential
    tasks:
      - id: log_operation_start
        type: io.kestra.plugin.jdbc.postgresql.Query
        sql: |
          INSERT INTO audit_log 
          (operation, user_id, start_time, parameters, execution_id)
          VALUES (
            'pii_data_extract',
            '<span class="hljs-template-variable">{{ execution.user }}</span>',
            NOW(),
            '<span class="hljs-template-variable">{{ inputs | tojson }}</span>',
            '<span class="hljs-template-variable">{{ execution.id }}</span>'
          )

      - id: perform_sensitive_operation
        type: io.kestra.plugin.jdbc.postgresql.Query
        sql: |
          SELECT 
            user_id,
            email,
            phone_number,
            masked_ssn
          FROM pii_data
          WHERE department = '<span class="hljs-template-variable">{{ inputs.department }}</span>'
        fetch: true

      - id: log_operation_complete
        type: io.kestra.plugin.jdbc.postgresql.Query
        sql: |
          UPDATE audit_log 
          SET 
            end_time = NOW(),
            row_count = <span class="hljs-template-variable">{{ outputs.perform_sensitive_operation.rowCount }}</span>,
            status = 'SUCCESS'
          WHERE execution_id = '<span class="hljs-template-variable">{{ execution.id }}</span>'</span>
</code></pre>
<h3 id="heading-plugin-testing-framework"><strong>Plugin Testing Framework</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/plugin-testing-framework.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">plugin-testing-pipeline</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">quality.assurance</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">plugin_name</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">test_scenarios</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">JSON</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">setup_test_environment</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">start_test_database</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.docker.Run</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:15</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">test</span>
          <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">test_db</span>
        <span class="hljs-attr">ports:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-number">5432</span><span class="hljs-string">:5432</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">maxDuration:</span> <span class="hljs-string">PT1M</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">start_test_api</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.docker.Run</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">mockserver/mockserver</span>
        <span class="hljs-attr">command:</span> <span class="hljs-string">-serverPort</span> <span class="hljs-number">1080</span>
        <span class="hljs-attr">ports:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-number">1080</span><span class="hljs-string">:1080</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">maxDuration:</span> <span class="hljs-string">PT1M</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_plugin_tests</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.test_scenarios }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"execute_test_<span class="hljs-template-variable">{{ taskloop.index }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.type }}</span>"</span>
        <span class="hljs-attr">cases:</span>
          <span class="hljs-attr">database:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_database_test</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
              <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:postgresql://localhost:5432/test_db</span>
              <span class="hljs-attr">username:</span> <span class="hljs-string">postgres</span>
              <span class="hljs-attr">password:</span> <span class="hljs-string">test</span>
              <span class="hljs-attr">sql:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.query }}</span>"</span>
              <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>

          <span class="hljs-attr">api:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">setup_api_mock</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
              <span class="hljs-attr">uri:</span> <span class="hljs-string">http://localhost:1080/mockserver/expectation</span>
              <span class="hljs-attr">method:</span> <span class="hljs-string">PUT</span>
              <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.mock_config | tojson }}</span>"</span>

            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">test_api_call</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
              <span class="hljs-attr">uri:</span> <span class="hljs-string">"http://localhost:1080<span class="hljs-template-variable">{{ task.value.endpoint }}</span>"</span>
              <span class="hljs-attr">method:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.method }}</span>"</span>
              <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.request_body | tojson }}</span>"</span>

          <span class="hljs-attr">performance:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_performance_test</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ range(1, task.value.iterations + 1) }}</span>"</span>
              <span class="hljs-attr">maxParallel:</span> <span class="hljs-number">10</span>
              <span class="hljs-attr">tasks:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"iteration_<span class="hljs-template-variable">{{ taskloop.value }}</span>"</span>
                  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
                  <span class="hljs-attr">format:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">collect_test_results</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
      import statistics
</span>
      <span class="hljs-comment"># Collect results from all tests</span>
      <span class="hljs-string">test_results</span> <span class="hljs-string">=</span> []

      {<span class="hljs-string">%</span> <span class="hljs-string">for</span> <span class="hljs-string">i</span> <span class="hljs-string">in</span> <span class="hljs-string">range(1</span>, <span class="hljs-string">inputs.test_scenarios|length</span> <span class="hljs-string">+</span> <span class="hljs-number">1</span><span class="hljs-string">)</span> <span class="hljs-string">%</span>}
      <span class="hljs-string">test_output</span> <span class="hljs-string">=</span> {{ <span class="hljs-string">outputs.run_plugin_tests.outputs</span>[<span class="hljs-string">'execute_test_'</span> <span class="hljs-string">+</span> <span class="hljs-string">i|string</span>] <span class="hljs-string">|</span> <span class="hljs-string">default(</span>{}<span class="hljs-string">)</span> <span class="hljs-string">|</span> <span class="hljs-string">tojson</span> }}
      <span class="hljs-attr">if test_output:</span>
          <span class="hljs-string">test_results.append({</span>
              <span class="hljs-attr">'scenario':</span> {{ <span class="hljs-string">i</span> }}<span class="hljs-string">,</span>
              <span class="hljs-attr">'success':</span> <span class="hljs-string">test_output.get('state')</span> <span class="hljs-string">==</span> <span class="hljs-string">'SUCCESS'</span><span class="hljs-string">,</span>
              <span class="hljs-attr">'duration':</span> <span class="hljs-string">test_output.get('duration',</span> <span class="hljs-number">0</span><span class="hljs-string">)</span>
          <span class="hljs-string">})</span>
      {<span class="hljs-string">%</span> <span class="hljs-string">endfor</span> <span class="hljs-string">%</span>}

      <span class="hljs-comment"># Calculate statistics</span>
      <span class="hljs-string">summary</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'total_tests':</span> <span class="hljs-string">len(test_results)</span>,
          <span class="hljs-attr">'passed_tests':</span> <span class="hljs-string">sum(1</span> <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">test_results</span> <span class="hljs-string">if</span> <span class="hljs-string">r</span>[<span class="hljs-string">'success'</span>]<span class="hljs-string">)</span>,
          <span class="hljs-attr">'failed_tests':</span> <span class="hljs-string">sum(1</span> <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">test_results</span> <span class="hljs-string">if</span> <span class="hljs-string">not</span> <span class="hljs-string">r</span>[<span class="hljs-string">'success'</span>]<span class="hljs-string">)</span>,
          <span class="hljs-attr">'pass_rate':</span> <span class="hljs-string">sum(1</span> <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">test_results</span> <span class="hljs-string">if</span> <span class="hljs-string">r</span>[<span class="hljs-string">'success'</span>]<span class="hljs-string">)</span> <span class="hljs-string">/</span> <span class="hljs-string">len(test_results)</span> <span class="hljs-string">*</span> <span class="hljs-number">100</span> <span class="hljs-string">if</span> <span class="hljs-string">test_results</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>,
          <span class="hljs-attr">'avg_duration':</span> <span class="hljs-string">statistics.mean(r</span>[<span class="hljs-string">'duration'</span>] <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">test_results)</span> <span class="hljs-string">if</span> <span class="hljs-string">test_results</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>,
          <span class="hljs-attr">'results':</span> <span class="hljs-string">test_results</span>
      }

      <span class="hljs-string">print(json.dumps(summary,</span> <span class="hljs-string">indent=2))</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_test_report</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.email.EmailSend</span>
    <span class="hljs-attr">to:</span> <span class="hljs-string">"qa-team@company.com"</span>
    <span class="hljs-attr">subject:</span> <span class="hljs-string">"Plugin Test Results - <span class="hljs-template-variable">{{ inputs.plugin_name }}</span>"</span>
    <span class="hljs-attr">htmlBody:</span> <span class="hljs-string">|
      &lt;h1&gt;Plugin Test Report&lt;/h1&gt;
      &lt;p&gt;&lt;strong&gt;Plugin:&lt;/strong&gt; {{ inputs.plugin_name }}&lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;Pass Rate:&lt;/strong&gt; {{ outputs.collect_test_results.vars.pass_rate | round(2) }}%&lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;Total Tests:&lt;/strong&gt; {{ outputs.collect_test_results.vars.total_tests }}&lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;Execution:&lt;/strong&gt; {{ execution.id }}&lt;/p&gt;</span>
</code></pre>
<hr />
<h2 id="heading-part-6-contributing-to-the-plugin-ecosystem"><strong>Part 6: Contributing to the Plugin Ecosystem</strong></h2>
<h3 id="heading-open-source-contribution-guide"><strong>Open Source Contribution Guide</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># 1. Fork the repository</span>
https://github.com/kestra-io/kestra

<span class="hljs-comment"># 2. Clone your fork</span>
git <span class="hljs-built_in">clone</span> https://github.com/YOUR_USERNAME/kestra.git
<span class="hljs-built_in">cd</span> kestra

<span class="hljs-comment"># 3. Create a feature branch</span>
git checkout -b feature/new-plugin

<span class="hljs-comment"># 4. Create plugin structure</span>
mkdir -p plugin-&lt;name&gt;/src/main/java/io/kestra/plugin/&lt;category&gt;
mkdir -p plugin-&lt;name&gt;/src/<span class="hljs-built_in">test</span>/java/io/kestra/plugin/&lt;category&gt;

<span class="hljs-comment"># 5. Implement your plugin</span>
<span class="hljs-comment"># (Follow existing plugin patterns)</span>

<span class="hljs-comment"># 6. Write tests</span>
<span class="hljs-comment"># (Aim for &gt;80% test coverage)</span>

<span class="hljs-comment"># 7. Update documentation</span>
<span class="hljs-comment"># - README.md in plugin directory</span>
<span class="hljs-comment"># - Add examples in @Plugin annotation</span>
<span class="hljs-comment"># - Update plugin list in docs</span>

<span class="hljs-comment"># 8. Run tests</span>
mvn <span class="hljs-built_in">test</span> -pl plugin-&lt;name&gt;

<span class="hljs-comment"># 9. Submit pull request</span>
git push origin feature/new-plugin
<span class="hljs-comment"># Create PR on GitHub</span>
</code></pre>
<h3 id="heading-plugin-contribution-checklist"><strong>Plugin Contribution Checklist</strong></h3>
<pre><code class="lang-markdown"><span class="hljs-section">## New Plugin Contribution Checklist</span>

<span class="hljs-section">### ✅ Code Quality</span>
<span class="hljs-bullet">-</span> [ ] Follows Kestra coding conventions
<span class="hljs-bullet">-</span> [ ] Proper error handling and logging
<span class="hljs-bullet">-</span> [ ] Input validation with @NotNull annotations
<span class="hljs-bullet">-</span> [ ] Comprehensive JavaDoc comments
<span class="hljs-bullet">-</span> [ ] No SonarQube issues

<span class="hljs-section">### ✅ Testing</span>
<span class="hljs-bullet">-</span> [ ] Unit tests for all public methods
<span class="hljs-bullet">-</span> [ ] Integration tests with real systems
<span class="hljs-bullet">-</span> [ ] Test edge cases and error conditions
<span class="hljs-bullet">-</span> [ ] Mock external dependencies
<span class="hljs-bullet">-</span> [ ] Test coverage &gt; 80%

<span class="hljs-section">### ✅ Documentation</span>
<span class="hljs-bullet">-</span> [ ] README.md with usage examples
<span class="hljs-bullet">-</span> [ ] @Plugin annotation with examples
<span class="hljs-bullet">-</span> [ ] Swagger annotations for all properties
<span class="hljs-bullet">-</span> [ ] Clear property descriptions
<span class="hljs-bullet">-</span> [ ] Example flows in /examples directory

<span class="hljs-section">### ✅ Configuration</span>
<span class="hljs-bullet">-</span> [ ] Proper Maven pom.xml
<span class="hljs-bullet">-</span> [ ] Plugin registration in parent pom
<span class="hljs-bullet">-</span> [ ] Version aligned with Kestra core
<span class="hljs-bullet">-</span> [ ] No conflicting dependencies

<span class="hljs-section">### ✅ Security</span>
<span class="hljs-bullet">-</span> [ ] Secrets marked with secret = true
<span class="hljs-bullet">-</span> [ ] No hardcoded credentials
<span class="hljs-bullet">-</span> [ ] Input sanitization
<span class="hljs-bullet">-</span> [ ] SSL/TLS support where applicable

<span class="hljs-section">### ✅ Performance</span>
<span class="hljs-bullet">-</span> [ ] Connection pooling for databases
<span class="hljs-bullet">-</span> [ ] Batch operations for bulk data
<span class="hljs-bullet">-</span> [ ] Memory-efficient streaming
<span class="hljs-bullet">-</span> [ ] Timeout configurations
</code></pre>
<h3 id="heading-community-plugin-gallery"><strong>Community Plugin Gallery</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># Example of a well-documented community plugin</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">community-plugin-showcase</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">demo.community</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Example: dbt plugin from community</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_dbt_models</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.dbt.cli.Run</span>
    <span class="hljs-attr">projectDir:</span> <span class="hljs-string">"s3://my-dbt-project/"</span>
    <span class="hljs-attr">profilesDir:</span> <span class="hljs-string">"s3://my-dbt-profiles/"</span>
    <span class="hljs-attr">target:</span> <span class="hljs-string">prod</span>
    <span class="hljs-attr">select:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">tag:hourly</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">model_name+</span>
    <span class="hljs-attr">exclude:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">tag:disabled</span>

  <span class="hljs-comment"># Example: Great Expectations plugin</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">data_quality_check</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.greatexpectations.Validation</span>
    <span class="hljs-attr">expectationSuite:</span> <span class="hljs-string">|
      {
        "expectation_suite_name": "customer_data",
        "expectations": [
          {
            "expectation_type": "expect_column_values_to_not_be_null",
            "kwargs": {"column": "customer_id"}
          }
        ]
      }
</span>    <span class="hljs-attr">data:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">file</span>
      <span class="hljs-attr">path:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.data_file }}</span>"</span>

  <span class="hljs-comment"># Example: Airbyte connector</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">sync_with_airbyte</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.airbyte.Sync</span>
    <span class="hljs-attr">connectionId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('AIRBYTE_CONNECTION_ID') }}</span>"</span>
    <span class="hljs-attr">workspaceId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('AIRBYTE_WORKSPACE_ID') }}</span>"</span>
    <span class="hljs-attr">apiUrl:</span> <span class="hljs-string">"https://api.airbyte.com"</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

  <span class="hljs-comment"># Example: Dagster integration</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">trigger_dagster_pipeline</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.dagster.Run</span>
    <span class="hljs-attr">repository:</span> <span class="hljs-string">my_repo</span>
    <span class="hljs-attr">pipeline:</span> <span class="hljs-string">process_data</span>
    <span class="hljs-attr">mode:</span> <span class="hljs-string">default</span>
    <span class="hljs-attr">runConfig:</span>
      <span class="hljs-attr">resources:</span>
        <span class="hljs-attr">io_manager:</span>
          <span class="hljs-attr">config:</span>
            <span class="hljs-attr">s3_bucket:</span> <span class="hljs-string">"dagster-output"</span>
</code></pre>
<hr />
<h2 id="heading-conclusion-becoming-a-plugin-master"><strong>Conclusion: Becoming a Plugin Master</strong></h2>
<h3 id="heading-key-takeaways"><strong>Key Takeaways</strong></h3>
<ol>
<li><p><strong>Kestra's plugin ecosystem is vast</strong>: 150+ plugins covering every data engineering need</p>
</li>
<li><p><strong>Custom plugins are straightforward</strong>: Java-based, with clear interfaces and patterns</p>
</li>
<li><p><strong>Enterprise integration is possible</strong>: Create plugins for any system, no matter how legacy</p>
</li>
<li><p><strong>Performance matters</strong>: Optimize plugins for production workloads</p>
</li>
<li><p><strong>Community contribution is valuable</strong>: Share your plugins to help others</p>
</li>
</ol>
<h3 id="heading-next-steps-for-you"><strong>Next Steps for You</strong></h3>
<ol>
<li><p><strong>Audit your data stack</strong>: What systems need plugins?</p>
</li>
<li><p><strong>Start simple</strong>: Create one custom plugin for an internal API</p>
</li>
<li><p><strong>Contribute</strong>: Find a missing plugin in the ecosystem and build it</p>
</li>
<li><p><strong>Share</strong>: Write a blog post about your plugin journey</p>
</li>
<li><p><strong>Optimize</strong>: Review existing plugins for performance improvements</p>
</li>
</ol>
<h3 id="heading-resources"><strong>Resources</strong></h3>
<ul>
<li><p><strong>Official Documentation</strong>: <a target="_blank" href="https://kestra.io/docs/plugins/develop">https://kestra.io/docs/plugins/develop</a></p>
</li>
<li><p><strong>Plugin Examples</strong>: <a target="_blank" href="https://github.com/kestra-io/examples">https://github.com/kestra-io/examples</a></p>
</li>
<li><p><strong>Community Forum</strong>: <a target="_blank" href="https://github.com/kestra-io/kestra/discussions">https://github.com/kestra-io/kestra/discussions</a></p>
</li>
<li><p><strong>Plugin Template</strong>: <a target="_blank" href="https://github.com/kestra-io/plugin-template">https://github.com/kestra-io/plugin-template</a></p>
</li>
</ul>
<hr />
<p>In the next article, we'll dive into <strong>Monitoring and Observability in Kestra</strong> - how to ensure your workflows are running smoothly and troubleshoot when they're not.</p>
<p><strong>Before the next article, try:</strong></p>
<ol>
<li><p>Create a simple custom plugin for a system you use</p>
</li>
<li><p>Optimize an existing plugin configuration for better performance</p>
</li>
<li><p>Contribute documentation or examples to an existing plugin</p>
</li>
<li><p>Set up a plugin testing pipeline for your team</p>
</li>
<li><p>Share your plugin creation experience in the Kestra community</p>
</li>
</ol>
<p>Remember: <strong>Every great data platform starts with great plugins</strong>. What will you build?</p>
<p>Happy plugin developing! 🛠️</p>
]]></content:encoded></item><item><title><![CDATA[Article 5: Advanced Workflow Patterns in Kestra.]]></title><description><![CDATA[Mastering Complex Orchestration Scenarios.
Introduction: The Orchestrator's Toolkit
Imagine you're conducting a symphony. You don't just wave your baton - you cue sections, adjust tempo, handle surprises, and ensure harmony. That's what advanced work...]]></description><link>https://techwasti.com/article-5-advanced-workflow-patterns-in-kestra</link><guid isPermaLink="true">https://techwasti.com/article-5-advanced-workflow-patterns-in-kestra</guid><category><![CDATA[#Kestra]]></category><category><![CDATA[dataengineering]]></category><category><![CDATA[ETL]]></category><category><![CDATA[etl-pipeline]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Testing]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Fri, 13 Feb 2026 18:30:22 GMT</pubDate><content:encoded><![CDATA[<p>Mastering Complex Orchestration Scenarios.</p>
<h2 id="heading-introduction-the-orchestrators-toolkit"><strong>Introduction: The Orchestrator's Toolkit</strong></h2>
<p>Imagine you're conducting a symphony. You don't just wave your baton - you cue sections, adjust tempo, handle surprises, and ensure harmony. That's what advanced workflow patterns give you in Kestra: the ability to conduct complex data orchestrations with precision and grace.</p>
<p>In this article, we'll move beyond simple linear pipelines into sophisticated orchestration patterns that handle real-world complexity. You'll learn patterns used by top data teams to build resilient, scalable, and intelligent workflows.</p>
<p><strong>What makes a workflow "advanced"?</strong></p>
<ul>
<li><p><strong>Intelligence</strong>: Making decisions based on data</p>
</li>
<li><p><strong>Resilience</strong>: Graceful handling of the unexpected</p>
</li>
<li><p><strong>Efficiency</strong>: Optimal resource usage and parallelism</p>
</li>
<li><p><strong>Adaptability</strong>: Dynamic behavior based on context</p>
</li>
<li><p><strong>Maintainability</strong>: Clean, reusable patterns</p>
</li>
</ul>
<hr />
<h2 id="heading-pattern-1-dynamic-parallel-processing"><strong>Pattern 1: Dynamic Parallel Processing</strong></h2>
<h3 id="heading-beyond-simple-parallel-tasks"><strong>Beyond Simple Parallel Tasks</strong></h3>
<p>While Kestra's <code>Parallel</code> task is great for fixed concurrency, real-world scenarios often require dynamic parallelism based on data volume or external factors.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">dynamic-parallel-processing</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.advanced</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Dynamic</span> <span class="hljs-string">parallelism</span> <span class="hljs-string">based</span> <span class="hljs-string">on</span> <span class="hljs-string">data</span> <span class="hljs-string">volume</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">discover_files</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.acme.com/files"</span>
    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">calculate_optimal_parallelism</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">files.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.discover_files.uri }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
      import math
</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('files.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">files</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

      <span class="hljs-string">total_files</span> <span class="hljs-string">=</span> <span class="hljs-string">len(files['files'])</span>
      <span class="hljs-string">avg_file_size</span> <span class="hljs-string">=</span> <span class="hljs-string">sum(f['size']</span> <span class="hljs-string">for</span> <span class="hljs-string">f</span> <span class="hljs-string">in</span> <span class="hljs-string">files['files'])</span> <span class="hljs-string">/</span> <span class="hljs-string">total_files</span> <span class="hljs-string">if</span> <span class="hljs-string">total_files</span> <span class="hljs-string">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>

      <span class="hljs-comment"># Dynamic parallelism calculation</span>
      <span class="hljs-comment"># Rule: 1 worker per 100MB, max 10 workers</span>
      <span class="hljs-string">max_workers</span> <span class="hljs-string">=</span> <span class="hljs-string">min(10,</span> <span class="hljs-string">max(1,</span> <span class="hljs-string">math.ceil((total_files</span> <span class="hljs-string">*</span> <span class="hljs-string">avg_file_size)</span> <span class="hljs-string">/</span> <span class="hljs-string">(100</span> <span class="hljs-string">*</span> <span class="hljs-number">1024</span> <span class="hljs-string">*</span> <span class="hljs-number">1024</span><span class="hljs-string">))))</span>

      <span class="hljs-comment"># Split files into batches</span>
      <span class="hljs-string">batch_size</span> <span class="hljs-string">=</span> <span class="hljs-string">math.ceil(total_files</span> <span class="hljs-string">/</span> <span class="hljs-string">max_workers)</span>
      <span class="hljs-string">batches</span> <span class="hljs-string">=</span> []
      <span class="hljs-string">for</span> <span class="hljs-string">i</span> <span class="hljs-string">in</span> <span class="hljs-string">range(0,</span> <span class="hljs-string">total_files,</span> <span class="hljs-string">batch_size):</span>
          <span class="hljs-string">batches.append(files['files'][i:i</span> <span class="hljs-string">+</span> <span class="hljs-string">batch_size])</span>

      <span class="hljs-string">print(json.dumps({</span>
          <span class="hljs-attr">'max_workers':</span> <span class="hljs-string">max_workers,</span>
          <span class="hljs-attr">'batch_size':</span> <span class="hljs-string">batch_size,</span>
          <span class="hljs-attr">'batches':</span> <span class="hljs-string">batches,</span>
          <span class="hljs-attr">'total_files':</span> <span class="hljs-string">total_files</span>
      <span class="hljs-string">}))</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_in_dynamic_parallel</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachParallel</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.calculate_optimal_parallelism.vars.batches }}</span>"</span>
    <span class="hljs-attr">maxParallel:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.calculate_optimal_parallelism.vars.max_workers }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"process_batch_<span class="hljs-template-variable">{{ taskloop.index }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">"Processing batch <span class="hljs-template-variable">{{ taskloop.index }}</span> with <span class="hljs-template-variable">{{ task.value | length }}</span> files"</span>
        <span class="hljs-attr">inputFiles:</span>
          <span class="hljs-attr">batch_files.json:</span> <span class="hljs-string">|
            {{ task.value | tojson }}
</span>        <span class="hljs-attr">script:</span> <span class="hljs-string">|
          import json
          import time
</span>
          <span class="hljs-string">with</span> <span class="hljs-string">open('batch_files.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
              <span class="hljs-string">files</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

          <span class="hljs-comment"># Simulate processing</span>
          <span class="hljs-string">results</span> <span class="hljs-string">=</span> []
          <span class="hljs-attr">for file in files:</span>
              <span class="hljs-string">time.sleep(0.1)</span>  <span class="hljs-comment"># Simulate work</span>
              <span class="hljs-string">results.append({</span>
                  <span class="hljs-attr">'file':</span> <span class="hljs-string">file['name'],</span>
                  <span class="hljs-attr">'processed':</span> <span class="hljs-literal">True</span><span class="hljs-string">,</span>
                  <span class="hljs-attr">'size':</span> <span class="hljs-string">file['size']</span>
              <span class="hljs-string">})</span>

          <span class="hljs-string">print(f"Processed</span> {<span class="hljs-string">len(results)</span>} <span class="hljs-string">files")</span>
          <span class="hljs-string">print(json.dumps({'results':</span> <span class="hljs-string">results}))</span>
</code></pre>
<h3 id="heading-fan-outfan-in-pattern"><strong>Fan-Out/Fan-In Pattern</strong></h3>
<p>This pattern distributes work across many workers (fan-out) and aggregates results (fan-in).</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">fan-out-fan-in-pattern</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.advanced</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Distribute</span> <span class="hljs-string">work,</span> <span class="hljs-string">process</span> <span class="hljs-string">in</span> <span class="hljs-string">parallel,</span> <span class="hljs-string">aggregate</span> <span class="hljs-string">results</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Fan-Out: Distribute work</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_work_items</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">&gt;
      {{ range(1, 101) | map('toString') | list | tojson }}
</span>
  <span class="hljs-comment"># Parallel Processing</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">parallel_processing</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachParallel</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.generate_work_items.value }}</span>"</span>
    <span class="hljs-attr">maxParallel:</span> <span class="hljs-number">20</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"process_item_<span class="hljs-template-variable">{{ task.value }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
        <span class="hljs-attr">script:</span> <span class="hljs-string">|
          import time
          import random
</span>
          <span class="hljs-comment"># Simulate variable processing time</span>
          <span class="hljs-string">processing_time</span> <span class="hljs-string">=</span> <span class="hljs-string">random.uniform(0.1,</span> <span class="hljs-number">2.0</span><span class="hljs-string">)</span>
          <span class="hljs-string">time.sleep(processing_time)</span>

          <span class="hljs-comment"># Generate result</span>
          <span class="hljs-string">result</span> <span class="hljs-string">=</span> {
              <span class="hljs-attr">'item':</span> {{ <span class="hljs-string">task.value</span> }},
              <span class="hljs-attr">'processed_at':</span> <span class="hljs-string">time.time()</span>,
              <span class="hljs-attr">'processing_time':</span> <span class="hljs-string">processing_time</span>,
              <span class="hljs-attr">'result':</span> {{ <span class="hljs-string">task.value</span> }} <span class="hljs-string">*</span> <span class="hljs-number">2</span>  <span class="hljs-comment"># Simple transformation</span>
          }

          <span class="hljs-string">print(f"Processed</span> <span class="hljs-string">item</span> {{ <span class="hljs-string">task.value</span> }} <span class="hljs-string">in</span> {<span class="hljs-string">processing_time:.2f</span>}<span class="hljs-string">s")</span>

          <span class="hljs-comment"># Return result</span>
          <span class="hljs-string">import</span> <span class="hljs-string">json</span>
          <span class="hljs-string">print(json.dumps(result))</span>

  <span class="hljs-comment"># Fan-In: Aggregate results</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">aggregate_results</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Aggregate all parallel results"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
      import statistics
</span>
      <span class="hljs-comment"># Collect all results from parallel tasks</span>
      <span class="hljs-string">all_results</span> <span class="hljs-string">=</span> []
      <span class="hljs-string">processing_times</span> <span class="hljs-string">=</span> []

      {<span class="hljs-string">%</span> <span class="hljs-string">for</span> <span class="hljs-string">i</span> <span class="hljs-string">in</span> <span class="hljs-string">range(1</span>, <span class="hljs-number">101</span><span class="hljs-string">)</span> <span class="hljs-string">%</span>}
      <span class="hljs-attr">try:</span>
          <span class="hljs-string">result_json</span> <span class="hljs-string">=</span> <span class="hljs-string">''</span><span class="hljs-string">'<span class="hljs-template-variable">{{ outputs.parallel_processing.outputs["process_item_" + i|string].vars | default('{}') | tojson }}</span>'</span><span class="hljs-string">''</span>
          <span class="hljs-attr">if result_json:</span>
              <span class="hljs-string">result</span> <span class="hljs-string">=</span> <span class="hljs-string">json.loads(result_json)</span>
              <span class="hljs-string">if</span> <span class="hljs-string">'result'</span> <span class="hljs-string">in</span> <span class="hljs-string">str(result):</span>  <span class="hljs-comment"># Check if result exists</span>
                  <span class="hljs-comment"># Extract the actual result from the output</span>
                  <span class="hljs-comment"># This depends on how your script outputs data</span>
                  <span class="hljs-string">all_results.append(result)</span>
                  <span class="hljs-string">if</span> <span class="hljs-string">'processing_time'</span> <span class="hljs-attr">in result:</span>
                      <span class="hljs-string">processing_times.append(result['processing_time'])</span>
      <span class="hljs-attr">except:</span>
          <span class="hljs-string">pass</span>
      {<span class="hljs-string">%</span> <span class="hljs-string">endfor</span> <span class="hljs-string">%</span>}

      <span class="hljs-comment"># Calculate statistics</span>
      <span class="hljs-string">stats</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'total_processed':</span> <span class="hljs-string">len(all_results)</span>,
          <span class="hljs-attr">'average_processing_time':</span> <span class="hljs-string">statistics.mean(processing_times)</span> <span class="hljs-string">if</span> <span class="hljs-string">processing_times</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>,
          <span class="hljs-attr">'total_processing_time':</span> <span class="hljs-string">sum(processing_times)</span>,
          <span class="hljs-attr">'throughput':</span> <span class="hljs-string">len(all_results)</span> <span class="hljs-string">/</span> <span class="hljs-string">(sum(processing_times)</span> <span class="hljs-string">if</span> <span class="hljs-string">processing_times</span> <span class="hljs-string">else</span> <span class="hljs-number">1</span><span class="hljs-string">)</span>,
          <span class="hljs-attr">'results_summary':</span> {
              <span class="hljs-attr">'min':</span> <span class="hljs-string">min(r</span>[<span class="hljs-string">'result'</span>] <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">all_results)</span> <span class="hljs-string">if</span> <span class="hljs-string">all_results</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>,
              <span class="hljs-attr">'max':</span> <span class="hljs-string">max(r</span>[<span class="hljs-string">'result'</span>] <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">all_results)</span> <span class="hljs-string">if</span> <span class="hljs-string">all_results</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>,
              <span class="hljs-attr">'avg':</span> <span class="hljs-string">statistics.mean(r</span>[<span class="hljs-string">'result'</span>] <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">all_results)</span> <span class="hljs-string">if</span> <span class="hljs-string">all_results</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>
          }
      }

      <span class="hljs-string">print(json.dumps(stats,</span> <span class="hljs-string">indent=2))</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">parallel_processing</span>
</code></pre>
<hr />
<h2 id="heading-pattern-2-circuit-breaker-pattern"><strong>Pattern 2: Circuit Breaker Pattern</strong></h2>
<h3 id="heading-preventing-cascading-failures"><strong>Preventing Cascading Failures</strong></h3>
<p>The circuit breaker pattern prevents systems from being overwhelmed by repeated failures, similar to an electrical circuit breaker.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">circuit-breaker-pattern</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.advanced</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Implement</span> <span class="hljs-string">circuit</span> <span class="hljs-string">breaker</span> <span class="hljs-string">for</span> <span class="hljs-string">unreliable</span> <span class="hljs-string">external</span> <span class="hljs-string">services</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">circuit_breaker_state_key:</span> <span class="hljs-string">"circuit_breaker:api.acme.com"</span>
  <span class="hljs-attr">failure_threshold:</span> <span class="hljs-number">5</span>
  <span class="hljs-attr">reset_timeout:</span> <span class="hljs-string">PT5M</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_circuit_breaker</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Get</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.circuit_breaker_state_key }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">evaluate_circuit_state</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.check_circuit_breaker.value.state | default('closed') }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">open:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">circuit_open_check_timeout</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
          <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
            {{ now().timestamp() - outputs.check_circuit_breaker.value.timestamp &gt; 300 }}
</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">attempt_reset</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
          <span class="hljs-attr">message:</span> <span class="hljs-string">"Circuit breaker open for <span class="hljs-template-variable">{{ vars.reset_timeout }}</span>. Testing if service recovered..."</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.circuit_open_check_timeout.value }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">skip_service_call</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
          <span class="hljs-attr">message:</span> <span class="hljs-string">"Circuit breaker is OPEN - skipping service call"</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ not outputs.circuit_open_check_timeout.value }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_external_service</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.acme.com/unstable-endpoint"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">GET</span>
    <span class="hljs-attr">retry:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
      <span class="hljs-attr">maxAttempt:</span> <span class="hljs-number">2</span>
      <span class="hljs-attr">delay:</span> <span class="hljs-string">PT5S</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT30S</span>
    <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
          {{ outputs.check_circuit_breaker.value.state != 'open' or 
             outputs.circuit_open_check_timeout.value == true }}
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_circuit_state</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_external_service.state.current }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">SUCCESS:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">reset_circuit_on_success</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Put</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.circuit_breaker_state_key }}</span>"</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">|
            {
              "state": "closed",
              "failure_count": 0,
              "last_success": "{{ now() }}",
              "timestamp": {{ now().timestamp() }}
            }
</span>          <span class="hljs-attr">ttl:</span> <span class="hljs-string">PT1H</span>

      <span class="hljs-attr">FAILED:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">get_current_failure_count</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Get</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.circuit_breaker_state_key }}</span>_failures"</span>
          <span class="hljs-attr">defaultValue:</span> <span class="hljs-string">"0"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">increment_failure_count</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Put</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.circuit_breaker_state_key }}</span>_failures"</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.get_current_failure_count.value | int + 1 }}</span>"</span>
          <span class="hljs-attr">ttl:</span> <span class="hljs-string">PT1H</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_if_should_trip</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
          <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
            {{ (outputs.get_current_failure_count.value | int + 1) &gt;= vars.failure_threshold }}
</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">trip_circuit_breaker</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Put</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.circuit_breaker_state_key }}</span>"</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">|
            {
              "state": "open",
              "tripped_at": "{{ now() }}",
              "failure_count": {{ outputs.get_current_failure_count.value | int + 1 }},
              "timestamp": {{ now().timestamp() }}
            }
</span>          <span class="hljs-attr">ttl:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.reset_timeout }}</span>"</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.check_if_should_trip.value }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">log_failure</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
          <span class="hljs-attr">message:</span> <span class="hljs-string">&gt;
            Service call failed. Failure count: {{ outputs.get_current_failure_count.value | int + 1 }}/{{ vars.failure_threshold }}
            {% if outputs.check_if_should_trip.value %}
            Circuit breaker TRIPPED to OPEN state
            {% endif %}
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">fallback_operation</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">"Using cached data or alternative service"</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
          {{ outputs.call_external_service.state.current == 'FAILED' or
             (outputs.check_circuit_breaker.value.state == 'open' and 
              not outputs.circuit_open_check_timeout.value) }}</span>
</code></pre>
<h3 id="heading-advanced-circuit-breaker-with-monitoring"><strong>Advanced Circuit Breaker with Monitoring</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">monitored-circuit-breaker</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.advanced</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">circuit_breaker_monitor</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import redis
      import json
      from datetime import datetime
</span>
      <span class="hljs-comment"># Connect to shared state store</span>
      <span class="hljs-string">r</span> <span class="hljs-string">=</span> <span class="hljs-string">redis.Redis(host='redis',</span> <span class="hljs-string">port=6379,</span> <span class="hljs-string">decode_responses=True)</span>

      <span class="hljs-comment"># Get all circuit breaker states</span>
      <span class="hljs-string">circuit_keys</span> <span class="hljs-string">=</span> <span class="hljs-string">r.keys("circuit_breaker:*")</span>
      <span class="hljs-string">circuits</span> <span class="hljs-string">=</span> []

      <span class="hljs-attr">for key in circuit_keys:</span>
          <span class="hljs-string">state</span> <span class="hljs-string">=</span> <span class="hljs-string">r.get(key)</span>
          <span class="hljs-attr">if state:</span>
              <span class="hljs-string">circuits.append({</span>
                  <span class="hljs-attr">'service':</span> <span class="hljs-string">key.replace('circuit_breaker:',</span> <span class="hljs-string">''</span><span class="hljs-string">),</span>
                  <span class="hljs-attr">'state':</span> <span class="hljs-string">json.loads(state),</span>
                  <span class="hljs-attr">'age_seconds':</span> <span class="hljs-string">datetime.now().timestamp()</span> <span class="hljs-bullet">-</span> <span class="hljs-string">json.loads(state).get('timestamp',</span> <span class="hljs-number">0</span><span class="hljs-string">)</span>
              <span class="hljs-string">})</span>

      <span class="hljs-comment"># Generate health report</span>
      <span class="hljs-string">report</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'timestamp':</span> <span class="hljs-string">datetime.now().isoformat()</span>,
          <span class="hljs-attr">'total_circuits':</span> <span class="hljs-string">len(circuits)</span>,
          <span class="hljs-attr">'open_circuits':</span> [<span class="hljs-string">c</span> <span class="hljs-string">for</span> <span class="hljs-string">c</span> <span class="hljs-string">in</span> <span class="hljs-string">circuits</span> <span class="hljs-string">if</span> <span class="hljs-string">c</span>[<span class="hljs-string">'state'</span>]<span class="hljs-string">.get('state')</span> <span class="hljs-string">==</span> <span class="hljs-string">'open'</span>],
          <span class="hljs-attr">'half_open_circuits':</span> [<span class="hljs-string">c</span> <span class="hljs-string">for</span> <span class="hljs-string">c</span> <span class="hljs-string">in</span> <span class="hljs-string">circuits</span> <span class="hljs-string">if</span> <span class="hljs-string">c</span>[<span class="hljs-string">'state'</span>]<span class="hljs-string">.get('state')</span> <span class="hljs-string">==</span> <span class="hljs-string">'half_open'</span>],
          <span class="hljs-attr">'closed_circuits':</span> [<span class="hljs-string">c</span> <span class="hljs-string">for</span> <span class="hljs-string">c</span> <span class="hljs-string">in</span> <span class="hljs-string">circuits</span> <span class="hljs-string">if</span> <span class="hljs-string">c</span>[<span class="hljs-string">'state'</span>]<span class="hljs-string">.get('state')</span> <span class="hljs-string">==</span> <span class="hljs-string">'closed'</span>],
          <span class="hljs-attr">'health_score':</span> <span class="hljs-string">len(</span>[<span class="hljs-string">c</span> <span class="hljs-string">for</span> <span class="hljs-string">c</span> <span class="hljs-string">in</span> <span class="hljs-string">circuits</span> <span class="hljs-string">if</span> <span class="hljs-string">c</span>[<span class="hljs-string">'state'</span>]<span class="hljs-string">.get('state')</span> <span class="hljs-string">==</span> <span class="hljs-string">'closed'</span>]<span class="hljs-string">)</span> <span class="hljs-string">/</span> <span class="hljs-string">len(circuits)</span> <span class="hljs-string">if</span> <span class="hljs-string">circuits</span> <span class="hljs-string">else</span> <span class="hljs-number">1.0</span>
      }

      <span class="hljs-comment"># Alert if health score drops below threshold</span>
      <span class="hljs-string">if</span> <span class="hljs-string">report['health_score']</span> <span class="hljs-string">&lt;</span> <span class="hljs-attr">0.8:</span>
          <span class="hljs-string">print("ALERT:</span> <span class="hljs-string">Circuit</span> <span class="hljs-string">breaker</span> <span class="hljs-string">health</span> <span class="hljs-string">score</span> <span class="hljs-string">below</span> <span class="hljs-number">80</span><span class="hljs-string">%")</span>

      <span class="hljs-string">print(json.dumps(report,</span> <span class="hljs-string">indent=2))</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_circuit_health_alert</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_WEBHOOK_URL') }}</span>"</span>
    <span class="hljs-attr">payload:</span> <span class="hljs-string">|
      {
        "blocks": [
          {
            "type": "header",
            "text": {
              "type": "plain_text",
              "text": "⚡ Circuit Breaker Health Report"
            }
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Health Score:* {{ outputs.circuit_breaker_monitor.vars.health_score | round(2) * 100 }}%\n*Open Circuits:* {{ outputs.circuit_breaker_monitor.vars.open_circuits | length }}\n*Total Circuits:* {{ outputs.circuit_breaker_monitor.vars.total_circuits }}"
            }
          }
        ]
      }
</span>    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.circuit_breaker_monitor.vars.health_score &lt; 0.8 }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-pattern-3-saga-pattern-for-distributed-transactions"><strong>Pattern 3: Saga Pattern for Distributed Transactions</strong></h2>
<h3 id="heading-implementing-compensating-transactions"><strong>Implementing Compensating Transactions</strong></h3>
<p>The Saga pattern manages distributed transactions by breaking them into a sequence of local transactions, each with a compensating action.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">saga-pattern-order-processing</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.ecommerce</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Distributed</span> <span class="hljs-string">transaction</span> <span class="hljs-string">with</span> <span class="hljs-string">compensation</span> <span class="hljs-string">logic</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">order_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">JSON</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># SAGA: Main transaction sequence</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">saga_orchestrator</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-comment"># Step 1: Reserve inventory (with compensation)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">reserve_inventory</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://inventory.acme.com/api/reserve"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">body:</span> <span class="hljs-string">|
          {
            "items": {{ inputs.order_data.items | tojson }},
            "order_id": "{{ execution.id }}"
          }
</span>        <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">false</span>

      <span class="hljs-comment"># Step 2: Process payment (with compensation)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_payment</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://payments.acme.com/api/charge"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">body:</span> <span class="hljs-string">|
          {
            "order_id": "{{ execution.id }}",
            "amount": {{ inputs.order_data.total }},
            "payment_method": {{ inputs.order_data.payment_method | tojson }}
          }
</span>        <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">false</span>

      <span class="hljs-comment"># Step 3: Schedule shipping (with compensation)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">schedule_shipping</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://shipping.acme.com/api/schedule"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">body:</span> <span class="hljs-string">|
          {
            "order_id": "{{ execution.id }}",
            "address": {{ inputs.order_data.shipping_address | tojson }},
            "items": {{ inputs.order_data.items | tojson }}
          }
</span>        <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">false</span>

      <span class="hljs-comment"># Step 4: Send confirmation (no compensation needed - idempotent)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_confirmation</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://notifications.acme.com/api/send"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">body:</span> <span class="hljs-string">|
          {
            "order_id": "{{ execution.id }}",
            "customer_email": "{{ inputs.order_data.customer_email }}",
            "template": "order_confirmation"
          }
</span>        <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">true</span>  <span class="hljs-comment"># Non-critical step</span>

  <span class="hljs-comment"># SAGA: Compensation handlers (run on failure)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">compensation_orchestrator</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-comment"># Compensate in reverse order</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cancel_shipping_if_scheduled</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://shipping.acme.com/api/cancel/<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">DELETE</span>
        <span class="hljs-attr">conditions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
            <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
              {{ outputs.saga_orchestrator.outputs.schedule_shipping != null and
                 outputs.saga_orchestrator.outputs.schedule_shipping.state.current == 'SUCCESS' }}
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">refund_payment_if_charged</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://payments.acme.com/api/refund/<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">conditions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
            <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
              {{ outputs.saga_orchestrator.outputs.process_payment != null and
                 outputs.saga_orchestrator.outputs.process_payment.state.current == 'SUCCESS' }}
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">release_inventory_if_reserved</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://inventory.acme.com/api/release/<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">DELETE</span>
        <span class="hljs-attr">conditions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
            <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
              {{ outputs.saga_orchestrator.outputs.reserve_inventory != null and
                 outputs.saga_orchestrator.outputs.reserve_inventory.state.current == 'SUCCESS' }}
</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.saga_orchestrator.state.current == 'FAILED' }}</span>"</span>

  <span class="hljs-comment"># SAGA: State persistence</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">persist_saga_state</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      INSERT INTO saga_states 
      (saga_id, execution_id, step, status, data, created_at, compensated)
      VALUES (
        'order_processing',
        '{{ execution.id }}',
        '{{ taskrun.id }}',
        '{{ taskrun.state.current }}',
        '{{ taskrun.value | tojson }}',
        NOW(),
        {{ outputs.compensation_orchestrator != null }}
      )
</span>    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"true"</span>  <span class="hljs-comment"># Always run after each task</span>
    <span class="hljs-attr">send:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">saga_orchestrator</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">compensation_orchestrator</span>
</code></pre>
<h3 id="heading-choreographed-saga-pattern"><strong>Choreographed Saga Pattern</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">choreographed-saga</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.advanced</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Event-driven</span> <span class="hljs-string">saga</span> <span class="hljs-string">pattern</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">start_saga</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">"SAGA_STARTED:<span class="hljs-template-variable">{{ execution.id }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">publish_order_created</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Produce</span>
    <span class="hljs-attr">topic:</span> <span class="hljs-string">"order-events"</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">|
      {
        "event_type": "ORDER_CREATED",
        "order_id": "{{ execution.id }}",
        "timestamp": "{{ now() }}",
        "data": {{ inputs.order_data | tojson }}
      }
</span>    <span class="hljs-attr">properties:</span>
      <span class="hljs-attr">bootstrap.servers:</span> <span class="hljs-string">"kafka:9092"</span>

  <span class="hljs-comment"># Each service listens to events and publishes its own</span>
  <span class="hljs-comment"># This is more decoupled but harder to monitor</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">monitor_saga_completion</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Consume</span>
    <span class="hljs-attr">topic:</span> <span class="hljs-string">"saga-completion-events"</span>
    <span class="hljs-attr">groupId:</span> <span class="hljs-string">"saga-monitor-<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
    <span class="hljs-attr">maxRecords:</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT5M</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.publish_order_created.state.current == 'SUCCESS' }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-pattern-4-event-sourcing-with-kestra"><strong>Pattern 4: Event Sourcing with Kestra</strong></h2>
<h3 id="heading-building-event-sourced-systems"><strong>Building Event-Sourced Systems</strong></h3>
<p>Event sourcing stores state changes as a sequence of events, providing audit trails and time-travel capabilities.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">event-sourced-shopping-cart</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.ecommerce</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Event-sourced</span> <span class="hljs-string">shopping</span> <span class="hljs-string">cart</span> <span class="hljs-string">implementation</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">cart_id</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"cart_<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">action</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">choices:</span> [<span class="hljs-string">"add_item"</span>, <span class="hljs-string">"remove_item"</span>, <span class="hljs-string">"update_quantity"</span>, <span class="hljs-string">"clear_cart"</span>, <span class="hljs-string">"checkout"</span>]
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">item_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">JSON</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Validate action based on current state</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_current_state</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      WITH current_state AS (
        SELECT 
          cart_id,
          SUM(CASE WHEN event_type = 'ITEM_ADDED' THEN 1 ELSE 0 END) as items_added,
          SUM(CASE WHEN event_type = 'ITEM_REMOVED' THEN 1 ELSE 0 END) as items_removed,
          MAX(occurred_at) as last_event_at
        FROM cart_events
        WHERE cart_id = '{{ inputs.cart_id }}'
        GROUP BY cart_id
      ),
      last_checkout AS (
        SELECT MAX(occurred_at) as checkout_time
        FROM cart_events
        WHERE cart_id = '{{ inputs.cart_id }}'
          AND event_type = 'CHECKOUT_COMPLETED'
      )
      SELECT 
        cs.cart_id,
        cs.items_added - cs.items_removed as current_item_count,
        lc.checkout_time
      FROM current_state cs
      LEFT JOIN last_checkout lc ON 1=1
</span>    <span class="hljs-attr">ignoreMissing:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_action</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.action }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">checkout:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_cart_not_empty</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
          <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
            {{ outputs.load_current_state.rows[0].current_item_count | default(0) &gt; 0 }}
</span>          <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Cannot checkout empty cart"</span>

      <span class="hljs-attr">add_item:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_item_data</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
          <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
            {{ inputs.item_data != null and 
               inputs.item_data.item_id and 
               inputs.item_data.quantity &gt; 0 }}
</span>          <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Invalid item data"</span>

  <span class="hljs-comment"># Store the event</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">store_event</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      INSERT INTO cart_events
      (event_id, cart_id, event_type, event_data, occurred_at, version)
      VALUES (
        '{{ execution.id }}',
        '{{ inputs.cart_id }}',
        '{{ inputs.action | upper }}',
        '{{ inputs.item_data | default({}) | tojson }}',
        NOW(),
        COALESCE(
          (SELECT MAX(version) + 1 
           FROM cart_events 
           WHERE cart_id = '{{ inputs.cart_id }}'),
          1
        )
      )
</span>
  <span class="hljs-comment"># Project current state from events</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">project_current_state</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Project current cart state from event stream"</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      SELECT * FROM cart_events 
      WHERE cart_id = '{{ inputs.cart_id }}'
      ORDER BY version
</span>    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
</span>
      <span class="hljs-comment"># Replay events to build current state</span>
      <span class="hljs-string">cart_state</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'cart_id':</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.cart_id }}</span>'</span>,
          <span class="hljs-attr">'items':</span> {},
          <span class="hljs-attr">'total_quantity':</span> <span class="hljs-number">0</span>,
          <span class="hljs-attr">'total_price':</span> <span class="hljs-number">0.0</span>,
          <span class="hljs-attr">'event_count':</span> <span class="hljs-number">0</span>
      }

      <span class="hljs-comment"># In a real implementation, you would fetch events from DB</span>
      <span class="hljs-comment"># For this example, we'll simulate event replay</span>

      <span class="hljs-comment"># Process based on action</span>
      <span class="hljs-string">action</span> <span class="hljs-string">=</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.action }}</span>'</span>
      <span class="hljs-string">item_data</span> <span class="hljs-string">=</span> {{ <span class="hljs-string">inputs.item_data</span> <span class="hljs-string">|</span> <span class="hljs-string">default('</span>{}<span class="hljs-string">') | tojson }}

      if action == '</span><span class="hljs-string">add_item':</span>
          <span class="hljs-string">item_id</span> <span class="hljs-string">=</span> <span class="hljs-string">item_data.get('item_id')</span>
          <span class="hljs-attr">if item_id:</span>
              <span class="hljs-string">if</span> <span class="hljs-string">item_id</span> <span class="hljs-string">not</span> <span class="hljs-string">in</span> <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>]<span class="hljs-string">:</span>
                  <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>][<span class="hljs-string">item_id</span>] <span class="hljs-string">=</span> {
                      <span class="hljs-attr">'quantity':</span> <span class="hljs-number">0</span>,
                      <span class="hljs-attr">'price':</span> <span class="hljs-string">item_data.get('price'</span>, <span class="hljs-number">0</span><span class="hljs-string">)</span>
                  }
              <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>][<span class="hljs-string">item_id</span>][<span class="hljs-string">'quantity'</span>] <span class="hljs-string">+=</span> <span class="hljs-string">item_data.get('quantity'</span>, <span class="hljs-number">1</span><span class="hljs-string">)</span>
              <span class="hljs-string">cart_state</span>[<span class="hljs-string">'total_quantity'</span>] <span class="hljs-string">+=</span> <span class="hljs-string">item_data.get('quantity'</span>, <span class="hljs-number">1</span><span class="hljs-string">)</span>
              <span class="hljs-string">cart_state</span>[<span class="hljs-string">'total_price'</span>] <span class="hljs-string">+=</span> <span class="hljs-string">item_data.get('price'</span>, <span class="hljs-number">0</span><span class="hljs-string">)</span> <span class="hljs-string">*</span> <span class="hljs-string">item_data.get('quantity'</span>, <span class="hljs-number">1</span><span class="hljs-string">)</span>

      <span class="hljs-string">elif</span> <span class="hljs-string">action</span> <span class="hljs-string">==</span> <span class="hljs-attr">'remove_item':</span>
          <span class="hljs-string">item_id</span> <span class="hljs-string">=</span> <span class="hljs-string">item_data.get('item_id')</span>
          <span class="hljs-string">if</span> <span class="hljs-string">item_id</span> <span class="hljs-string">in</span> <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>]<span class="hljs-string">:</span>
              <span class="hljs-string">quantity_to_remove</span> <span class="hljs-string">=</span> <span class="hljs-string">min(</span>
                  <span class="hljs-string">item_data.get('quantity'</span>, <span class="hljs-number">1</span><span class="hljs-string">)</span>,
                  <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>][<span class="hljs-string">item_id</span>][<span class="hljs-string">'quantity'</span>]
              <span class="hljs-string">)</span>
              <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>][<span class="hljs-string">item_id</span>][<span class="hljs-string">'quantity'</span>] <span class="hljs-string">-=</span> <span class="hljs-string">quantity_to_remove</span>
              <span class="hljs-string">cart_state</span>[<span class="hljs-string">'total_quantity'</span>] <span class="hljs-string">-=</span> <span class="hljs-string">quantity_to_remove</span>
              <span class="hljs-string">cart_state</span>[<span class="hljs-string">'total_price'</span>] <span class="hljs-string">-=</span> <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>][<span class="hljs-string">item_id</span>][<span class="hljs-string">'price'</span>] <span class="hljs-string">*</span> <span class="hljs-string">quantity_to_remove</span>

              <span class="hljs-string">if</span> <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>][<span class="hljs-string">item_id</span>][<span class="hljs-string">'quantity'</span>] <span class="hljs-string">&lt;=</span> <span class="hljs-attr">0:</span>
                  <span class="hljs-string">del</span> <span class="hljs-string">cart_state</span>[<span class="hljs-string">'items'</span>][<span class="hljs-string">item_id</span>]

      <span class="hljs-string">cart_state</span>[<span class="hljs-string">'event_count'</span>] <span class="hljs-string">+=</span> <span class="hljs-number">1</span>

      <span class="hljs-string">print(json.dumps(cart_state</span>, <span class="hljs-string">indent=2))</span>

  <span class="hljs-comment"># Publish event to event bus</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">publish_to_event_bus</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Produce</span>
    <span class="hljs-attr">topic:</span> <span class="hljs-string">"cart-events"</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.cart_id }}</span>"</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">|
      {
        "cart_id": "{{ inputs.cart_id }}",
        "event_type": "{{ inputs.action | upper }}",
        "event_data": {{ inputs.item_data | default({}) | tojson }},
        "timestamp": "{{ now() }}",
        "event_id": "{{ execution.id }}"
      }
</span>    <span class="hljs-attr">properties:</span>
      <span class="hljs-attr">bootstrap.servers:</span> <span class="hljs-string">"kafka:9092"</span>
</code></pre>
<h3 id="heading-time-travel-query-pattern"><strong>Time-Travel Query Pattern</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">time-travel-query</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.advanced</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Query</span> <span class="hljs-string">historical</span> <span class="hljs-string">state</span> <span class="hljs-string">at</span> <span class="hljs-string">a</span> <span class="hljs-string">specific</span> <span class="hljs-string">point</span> <span class="hljs-string">in</span> <span class="hljs-string">time</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">entity_id</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">as_of_time</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">replay_events_to_timestamp</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      SELECT 
        event_type,
        event_data,
        occurred_at,
        version
      FROM entity_events
      WHERE entity_id = '{{ inputs.entity_id }}'
        AND occurred_at &lt;= '{{ inputs.as_of_time }}'
      ORDER BY version
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">reconstruct_historical_state</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">events.json:</span> <span class="hljs-string">|
        {{ outputs.replay_events_to_timestamp.rows | tojson }}
</span>    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('events.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">events</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

      <span class="hljs-comment"># Start with empty state</span>
      <span class="hljs-string">state</span> <span class="hljs-string">=</span> {}

      <span class="hljs-comment"># Replay events in order</span>
      <span class="hljs-attr">for event in events:</span>
          <span class="hljs-string">event_type</span> <span class="hljs-string">=</span> <span class="hljs-string">event['event_type']</span>
          <span class="hljs-string">event_data</span> <span class="hljs-string">=</span> <span class="hljs-string">event['event_data']</span>

          <span class="hljs-string">if</span> <span class="hljs-string">event_type</span> <span class="hljs-string">==</span> <span class="hljs-attr">'ENTITY_CREATED':</span>
              <span class="hljs-string">state</span> <span class="hljs-string">=</span> <span class="hljs-string">event_data</span>
          <span class="hljs-string">elif</span> <span class="hljs-string">event_type</span> <span class="hljs-string">==</span> <span class="hljs-attr">'ENTITY_UPDATED':</span>
              <span class="hljs-string">state.update(event_data)</span>
          <span class="hljs-string">elif</span> <span class="hljs-string">event_type</span> <span class="hljs-string">==</span> <span class="hljs-attr">'FIELD_SET':</span>
              <span class="hljs-string">field_name</span> <span class="hljs-string">=</span> <span class="hljs-string">event_data['field']</span>
              <span class="hljs-string">state[field_name]</span> <span class="hljs-string">=</span> <span class="hljs-string">event_data['value']</span>
          <span class="hljs-string">elif</span> <span class="hljs-string">event_type</span> <span class="hljs-string">==</span> <span class="hljs-attr">'FIELD_INCREMENTED':</span>
              <span class="hljs-string">field_name</span> <span class="hljs-string">=</span> <span class="hljs-string">event_data['field']</span>
              <span class="hljs-attr">if field_name in state:</span>
                  <span class="hljs-string">state[field_name]</span> <span class="hljs-string">+=</span> <span class="hljs-string">event_data['amount']</span>
              <span class="hljs-attr">else:</span>
                  <span class="hljs-string">state[field_name]</span> <span class="hljs-string">=</span> <span class="hljs-string">event_data['amount']</span>
          <span class="hljs-comment"># Add more event handlers as needed</span>

      <span class="hljs-string">result</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'entity_id':</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.entity_id }}</span>'</span>,
          <span class="hljs-attr">'as_of_time':</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.as_of_time }}</span>'</span>,
          <span class="hljs-attr">'state':</span> <span class="hljs-string">state</span>,
          <span class="hljs-attr">'events_replayed':</span> <span class="hljs-string">len(events)</span>,
          <span class="hljs-attr">'last_event_time':</span> <span class="hljs-string">events</span>[<span class="hljs-number">-1</span>][<span class="hljs-string">'occurred_at'</span>] <span class="hljs-string">if</span> <span class="hljs-string">events</span> <span class="hljs-string">else</span> <span class="hljs-string">None</span>
      }

      <span class="hljs-string">print(json.dumps(result,</span> <span class="hljs-string">indent=2))</span>
</code></pre>
<hr />
<h2 id="heading-pattern-5-cqrs-command-query-responsibility-segregation"><strong>Pattern 5: CQRS (Command Query Responsibility Segregation)</strong></h2>
<h3 id="heading-separating-read-and-write-models"><strong>Separating Read and Write Models</strong></h3>
<p>CQRS separates read and write operations for better scalability and optimization.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">cqrs-user-profile</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.social</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">CQRS</span> <span class="hljs-string">pattern</span> <span class="hljs-string">for</span> <span class="hljs-string">user</span> <span class="hljs-string">profiles</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">command</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">choices:</span> [<span class="hljs-string">"update_profile"</span>, <span class="hljs-string">"get_profile"</span>, <span class="hljs-string">"search_profiles"</span>]
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">user_id</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">profile_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">JSON</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Command Side: Handle writes</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">handle_command</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.command }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">update_profile:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_profile_data</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
          <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
            {{ inputs.profile_data != null and 
               inputs.user_id != null }}
</span>          <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Missing profile data or user_id"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">store_write_event</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
          <span class="hljs-attr">sql:</span> <span class="hljs-string">|
            INSERT INTO user_profile_events
            (event_id, user_id, event_type, event_data, occurred_at)
            VALUES (
              '{{ execution.id }}',
              '{{ inputs.user_id }}',
              'PROFILE_UPDATED',
              '{{ inputs.profile_data | tojson }}',
              NOW()
            )
</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_read_model_async</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
          <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.background</span>
          <span class="hljs-attr">flowId:</span> <span class="hljs-string">update-profile-read-model</span>
          <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>  <span class="hljs-comment"># Fire and forget - eventual consistency</span>
          <span class="hljs-attr">transmit:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">user_id</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.user_id }}</span>"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">profile_data</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.profile_data | tojson }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">invalidate_cache</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.redis.Delete</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"profile:<span class="hljs-template-variable">{{ inputs.user_id }}</span>"</span>
          <span class="hljs-attr">host:</span> <span class="hljs-string">"redis"</span>
          <span class="hljs-attr">port:</span> <span class="hljs-number">6379</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_profile_updated_event</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Produce</span>
          <span class="hljs-attr">topic:</span> <span class="hljs-string">"user-profile-events"</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.user_id }}</span>"</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">|
            {
              "event_type": "PROFILE_UPDATED",
              "user_id": "{{ inputs.user_id }}",
              "timestamp": "{{ now() }}",
              "event_id": "{{ execution.id }}"
            }
</span>
  <span class="hljs-comment"># Query Side: Handle reads (optimized for reading)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">handle_query</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.command }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">get_profile:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">try_cache_first</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.redis.Get</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"profile:<span class="hljs-template-variable">{{ inputs.user_id }}</span>"</span>
          <span class="hljs-attr">host:</span> <span class="hljs-string">"redis"</span>
          <span class="hljs-attr">port:</span> <span class="hljs-number">6379</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cache_hit</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
          <span class="hljs-attr">format:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.try_cache_first.value }}</span>"</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.try_cache_first.value != null }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cache_miss_query</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
          <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">sql:</span> <span class="hljs-string">|
            SELECT 
              user_id,
              display_name,
              avatar_url,
              bio,
              location,
              website,
              follower_count,
              following_count,
              post_count,
              last_updated
            FROM user_profiles_read
            WHERE user_id = '{{ inputs.user_id }}'
</span>          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.try_cache_first.value == null }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">populate_cache</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.redis.Put</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">"profile:<span class="hljs-template-variable">{{ inputs.user_id }}</span>"</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">|
            {{ outputs.cache_miss_query.rows[0] | tojson }}
</span>          <span class="hljs-attr">ttl:</span> <span class="hljs-string">PT1H</span>
          <span class="hljs-attr">host:</span> <span class="hljs-string">"redis"</span>
          <span class="hljs-attr">port:</span> <span class="hljs-number">6379</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
                {{ outputs.cache_miss_query.rows | length &gt; 0 }}
</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">return_profile</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
          <span class="hljs-attr">format:</span> <span class="hljs-string">&gt;
            {{ outputs.cache_miss_query.rows[0] | tojson if outputs.cache_miss_query.rows else '{}' }}
</span>          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.try_cache_first.value == null }}</span>"</span>

      <span class="hljs-attr">search_profiles:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">search_read_model</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.elasticsearch.Search</span>
          <span class="hljs-attr">connection:</span>
            <span class="hljs-attr">hosts:</span> [<span class="hljs-string">"elasticsearch:9200"</span>]
          <span class="hljs-attr">index:</span> <span class="hljs-string">"user_profiles"</span>
          <span class="hljs-attr">query:</span> <span class="hljs-string">|
            {
              "query": {
                "multi_match": {
                  "query": "{{ inputs.profile_data.query }}",
                  "fields": ["display_name^2", "bio", "location"]
                }
              },
              "size": 20
            }
</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">return_search_results</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
          <span class="hljs-attr">format:</span> <span class="hljs-string">&gt;</span>
            {{ <span class="hljs-string">outputs.search_read_model.hits</span> <span class="hljs-string">|</span> <span class="hljs-string">tojson</span> }}
</code></pre>
<h3 id="heading-read-model-update-processor"><strong>Read Model Update Processor</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">update-profile-read-model</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.background</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Background</span> <span class="hljs-string">processor</span> <span class="hljs-string">to</span> <span class="hljs-string">update</span> <span class="hljs-string">read</span> <span class="hljs-string">models</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">listen_for_profile_events</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Consume</span>
    <span class="hljs-attr">topic:</span> <span class="hljs-string">"user-profile-events"</span>
    <span class="hljs-attr">groupId:</span> <span class="hljs-string">"read-model-updater"</span>
    <span class="hljs-attr">maxRecords:</span> <span class="hljs-number">100</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT10S</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_events_batch</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.listen_for_profile_events.messages }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"process_event_<span class="hljs-template-variable">{{ taskloop.index }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.value.event_type }}</span>"</span>
        <span class="hljs-attr">cases:</span>
          <span class="hljs-attr">PROFILE_UPDATED:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_read_model</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
              <span class="hljs-attr">sql:</span> <span class="hljs-string">|
                INSERT INTO user_profiles_read
                (user_id, display_name, avatar_url, bio, location, website, last_updated)
                VALUES (
                  '{{ task.value.value.user_id }}',
                  '{{ task.value.value.event_data.display_name | default('') }}',
                  '{{ task.value.value.event_data.avatar_url | default('') }}',
                  '{{ task.value.value.event_data.bio | default('') }}',
                  '{{ task.value.value.event_data.location | default('') }}',
                  '{{ task.value.value.event_data.website | default('') }}',
                  NOW()
                )
                ON CONFLICT (user_id) DO UPDATE SET
                  display_name = EXCLUDED.display_name,
                  avatar_url = EXCLUDED.avatar_url,
                  bio = EXCLUDED.bio,
                  location = EXCLUDED.location,
                  website = EXCLUDED.website,
                  last_updated = NOW()
</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_search_index</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.elasticsearch.Index</span>
              <span class="hljs-attr">connection:</span>
                <span class="hljs-attr">hosts:</span> [<span class="hljs-string">"elasticsearch:9200"</span>]
              <span class="hljs-attr">index:</span> <span class="hljs-string">"user_profiles"</span>
              <span class="hljs-attr">id:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.value.user_id }}</span>"</span>
              <span class="hljs-attr">document:</span> <span class="hljs-string">|
                {
                  "user_id": "{{ task.value.value.user_id }}",
                  "display_name": "{{ task.value.value.event_data.display_name | default('') }}",
                  "bio": "{{ task.value.value.event_data.bio | default('') }}",
                  "location": "{{ task.value.value.event_data.location | default('') }}",
                  "last_updated": "{{ now() }}"
                }
</span>
          <span class="hljs-attr">FOLLOW_ADDED:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">increment_follower_count</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
              <span class="hljs-attr">sql:</span> <span class="hljs-string">|
                UPDATE user_profiles_read
                SET follower_count = follower_count + 1
                WHERE user_id = '{{ task.value.value.target_user_id }}'
</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">increment_following_count</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
              <span class="hljs-attr">sql:</span> <span class="hljs-string">|
                UPDATE user_profiles_read
                SET following_count = following_count + 1
                WHERE user_id = '{{ task.value.value.source_user_id }}'
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">commit_kafka_offsets</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.kafka.Consume</span>
    <span class="hljs-attr">topic:</span> <span class="hljs-string">"user-profile-events"</span>
    <span class="hljs-attr">groupId:</span> <span class="hljs-string">"read-model-updater"</span>
    <span class="hljs-attr">maxRecords:</span> <span class="hljs-number">0</span>  <span class="hljs-comment"># Just commit offsets</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.listen_for_profile_events.messages | length &gt; 0 }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-pattern-6-strangler-fig-pattern"><strong>Pattern 6: Strangler Fig Pattern</strong></h2>
<h3 id="heading-incremental-migration-strategy"><strong>Incremental Migration Strategy</strong></h3>
<p>The Strangler Fig pattern incrementally replaces a legacy system by building new functionality around it, then gradually migrating.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">strangler-fig-migration</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.migration</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Incrementally</span> <span class="hljs-string">migrate</span> <span class="hljs-string">from</span> <span class="hljs-string">legacy</span> <span class="hljs-string">to</span> <span class="hljs-string">new</span> <span class="hljs-string">system</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">migration_phase:</span> <span class="hljs-string">"parallel_run"</span>  <span class="hljs-comment"># Options: shadow, parallel_run, cutover</span>
  <span class="hljs-attr">traffic_percentage:</span> <span class="hljs-number">50</span>  <span class="hljs-comment"># Percentage of traffic to send to new system</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Feature Flag: Route traffic based on migration phase</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">route_traffic</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.migration_phase }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">shadow:</span>
        <span class="hljs-comment"># Shadow mode: Send to both, compare results</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_legacy_system</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://legacy-api.acme.com/process"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_body }}</span>"</span>
          <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_new_system_shadow</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://new-api.acme.com/process"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_body }}</span>"</span>
          <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">compare_results</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
          <span class="hljs-attr">inputFiles:</span>
            <span class="hljs-attr">legacy_response.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_legacy_system.uri }}</span>"</span>
            <span class="hljs-attr">new_response.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_new_system_shadow.uri }}</span>"</span>
          <span class="hljs-attr">script:</span> <span class="hljs-string">|
            import json
            import difflib
</span>
            <span class="hljs-string">with</span> <span class="hljs-string">open('legacy_response.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
                <span class="hljs-string">legacy</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>
            <span class="hljs-string">with</span> <span class="hljs-string">open('new_response.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
                <span class="hljs-string">new</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

            <span class="hljs-comment"># Compare results</span>
            <span class="hljs-string">comparison</span> <span class="hljs-string">=</span> {
                <span class="hljs-attr">'match':</span> <span class="hljs-string">legacy</span> <span class="hljs-string">==</span> <span class="hljs-string">new</span>,
                <span class="hljs-attr">'legacy_response':</span> <span class="hljs-string">legacy</span>,
                <span class="hljs-attr">'new_response':</span> <span class="hljs-string">new</span>,
                <span class="hljs-attr">'differences':</span> []
            }

            <span class="hljs-string">if</span> <span class="hljs-string">legacy</span> <span class="hljs-type">!=</span> <span class="hljs-attr">new:</span>
                <span class="hljs-comment"># Find specific differences</span>
                <span class="hljs-string">legacy_str</span> <span class="hljs-string">=</span> <span class="hljs-string">json.dumps(legacy,</span> <span class="hljs-string">sort_keys=True,</span> <span class="hljs-string">indent=2)</span>
                <span class="hljs-string">new_str</span> <span class="hljs-string">=</span> <span class="hljs-string">json.dumps(new,</span> <span class="hljs-string">sort_keys=True,</span> <span class="hljs-string">indent=2)</span>

                <span class="hljs-string">diff</span> <span class="hljs-string">=</span> <span class="hljs-string">list(difflib.unified_diff(</span>
                    <span class="hljs-string">legacy_str.splitlines(),</span>
                    <span class="hljs-string">new_str.splitlines(),</span>
                    <span class="hljs-string">lineterm=''</span>
                <span class="hljs-string">))</span>

                <span class="hljs-string">comparison['differences']</span> <span class="hljs-string">=</span> <span class="hljs-string">diff[:10]</span>  <span class="hljs-comment"># First 10 differences</span>

            <span class="hljs-string">print(json.dumps(comparison,</span> <span class="hljs-string">indent=2))</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">return_legacy_response</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
          <span class="hljs-attr">format:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_legacy_system.body | tojson }}</span>"</span>

      <span class="hljs-attr">parallel_run:</span>
        <span class="hljs-comment"># Parallel run: Split traffic between systems</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">decide_routing</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
          <span class="hljs-attr">format:</span> <span class="hljs-string">&gt;
            {{ random() * 100 &lt;= vars.traffic_percentage }}
</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_new_system_parallel</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://new-api.acme.com/process"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_body }}</span>"</span>
          <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.decide_routing.value == true }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_legacy_system_fallback</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://legacy-api.acme.com/process"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_body }}</span>"</span>
          <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.decide_routing.value == false }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">return_response</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.decide_routing.value }}</span>"</span>
          <span class="hljs-attr">cases:</span>
            <span class="hljs-attr">true:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">return_new_response</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
                <span class="hljs-attr">format:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_new_system_parallel.body | tojson }}</span>"</span>
            <span class="hljs-attr">false:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">return_legacy_response_fallback</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
                <span class="hljs-attr">format:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_legacy_system_fallback.body | tojson }}</span>"</span>

      <span class="hljs-attr">cutover:</span>
        <span class="hljs-comment"># Cutover: All traffic to new system</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_new_system_primary</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://new-api.acme.com/process"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_body }}</span>"</span>
          <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">return_new_response_primary</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
          <span class="hljs-attr">format:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_new_system_primary.body | tojson }}</span>"</span>

        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">legacy_system_backup</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://legacy-api.acme.com/process"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_body }}</span>"</span>
          <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">true</span>  <span class="hljs-comment"># Legacy might be turned off</span>

  <span class="hljs-comment"># Migration Monitoring</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">track_migration_metrics</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      INSERT INTO migration_metrics
      (migration_phase, traffic_percentage, request_count, 
       success_count, error_count, avg_response_time, timestamp)
      SELECT 
        '{{ vars.migration_phase }}',
        {{ vars.traffic_percentage }},
        COUNT(*) as request_count,
        SUM(CASE WHEN status_code = 200 THEN 1 ELSE 0 END) as success_count,
        SUM(CASE WHEN status_code != 200 THEN 1 ELSE 0 END) as error_count,
        AVG(response_time_ms) as avg_response_time,
        NOW()
      FROM api_requests
      WHERE timestamp &gt;= NOW() - INTERVAL '1 minute'
</span>    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.migration_phase != 'completed' }}</span>"</span>
</code></pre>
<h3 id="heading-data-migration-with-dual-write"><strong>Data Migration with Dual Write</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">dual-write-migration</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.migration</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Dual</span> <span class="hljs-string">write</span> <span class="hljs-string">to</span> <span class="hljs-string">both</span> <span class="hljs-string">old</span> <span class="hljs-string">and</span> <span class="hljs-string">new</span> <span class="hljs-string">databases</span> <span class="hljs-string">during</span> <span class="hljs-string">migration</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">write_to_both_systems</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">write_to_legacy</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">"jdbc:postgresql://legacy-db.acme.com:5432/app"</span>
        <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('LEGACY_DB_USER') }}</span>"</span>
        <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('LEGACY_DB_PASSWORD') }}</span>"</span>
        <span class="hljs-attr">sql:</span> <span class="hljs-string">|
          INSERT INTO orders 
          (order_id, customer_id, amount, status, created_at)
          VALUES (
            '{{ inputs.order_id }}',
            '{{ inputs.customer_id }}',
            {{ inputs.amount }},
            'pending',
            NOW()
          )
</span>        <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">false</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">write_to_new</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">"jdbc:postgresql://new-db.acme.com:5432/app"</span>
        <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('NEW_DB_USER') }}</span>"</span>
        <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('NEW_DB_PASSWORD') }}</span>"</span>
        <span class="hljs-attr">sql:</span> <span class="hljs-string">|
          INSERT INTO orders 
          (order_id, customer_id, amount, status, created_at, metadata)
          VALUES (
            '{{ inputs.order_id }}',
            '{{ inputs.customer_id }}',
            {{ inputs.amount }},
            'pending',
            NOW(),
            '{"migration_phase": "dual_write"}'
          )
</span>        <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">true</span>  <span class="hljs-comment"># New system might have issues</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">verify_data_consistency</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Verify data was written to both systems consistently"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import psycopg2
      import json
</span>
      <span class="hljs-string">def</span> <span class="hljs-string">query_db(host,</span> <span class="hljs-string">dbname,</span> <span class="hljs-string">user,</span> <span class="hljs-string">password,</span> <span class="hljs-string">query):</span>
          <span class="hljs-string">conn</span> <span class="hljs-string">=</span> <span class="hljs-string">psycopg2.connect(</span>
              <span class="hljs-string">host=host,</span> <span class="hljs-string">dbname=dbname,</span> <span class="hljs-string">user=user,</span> <span class="hljs-string">password=password</span>
          <span class="hljs-string">)</span>
          <span class="hljs-string">cur</span> <span class="hljs-string">=</span> <span class="hljs-string">conn.cursor()</span>
          <span class="hljs-string">cur.execute(query)</span>
          <span class="hljs-string">result</span> <span class="hljs-string">=</span> <span class="hljs-string">cur.fetchone()</span>
          <span class="hljs-string">cur.close()</span>
          <span class="hljs-string">conn.close()</span>
          <span class="hljs-string">return</span> <span class="hljs-string">result</span>

      <span class="hljs-comment"># Query both systems</span>
      <span class="hljs-string">legacy_query</span> <span class="hljs-string">=</span> <span class="hljs-string">f"""</span>
          <span class="hljs-string">SELECT</span> <span class="hljs-string">order_id,</span> <span class="hljs-string">customer_id,</span> <span class="hljs-string">amount,</span> <span class="hljs-string">status</span>
          <span class="hljs-string">FROM</span> <span class="hljs-string">orders</span>
          <span class="hljs-string">WHERE</span> <span class="hljs-string">order_id</span> <span class="hljs-string">=</span> <span class="hljs-string">'{ <span class="hljs-template-variable">{{ inputs.order_id }}</span> }'</span>
      <span class="hljs-string">""</span><span class="hljs-string">"

      new_query = f"</span><span class="hljs-string">""</span>
          <span class="hljs-string">SELECT</span> <span class="hljs-string">order_id,</span> <span class="hljs-string">customer_id,</span> <span class="hljs-string">amount,</span> <span class="hljs-string">status</span>
          <span class="hljs-string">FROM</span> <span class="hljs-string">orders</span>
          <span class="hljs-string">WHERE</span> <span class="hljs-string">order_id</span> <span class="hljs-string">=</span> <span class="hljs-string">'{ <span class="hljs-template-variable">{{ inputs.order_id }}</span> }'</span>
      <span class="hljs-string">""</span><span class="hljs-string">"

      try:
          legacy_result = query_db(
              'legacy-db.acme.com', 'app',
              '<span class="hljs-template-variable">{{ secret('LEGACY_DB_USER') }}</span>', '<span class="hljs-template-variable">{{ secret('LEGACY_DB_PASSWORD') }}</span>',
              legacy_query
          )
      except Exception as e:
          legacy_result = None
          print(f"</span><span class="hljs-attr">Legacy query failed:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")

      try:
          new_result = query_db(
              'new-db.acme.com', 'app',
              '<span class="hljs-template-variable">{{ secret('NEW_DB_USER') }}</span>', '<span class="hljs-template-variable">{{ secret('NEW_DB_PASSWORD') }}</span>',
              new_query
          )
      except Exception as e:
          new_result = None
          print(f"</span><span class="hljs-attr">New query failed:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")

      # Compare results
      comparison = {
          'order_id': '<span class="hljs-template-variable">{{ inputs.order_id }}</span>',
          'legacy_found': legacy_result is not None,
          'new_found': new_result is not None,
          'consistent': legacy_result == new_result,
          'legacy_data': legacy_result,
          'new_data': new_result
      }

      print(json.dumps(comparison, indent=2))

      if not comparison['consistent']:
          # Log inconsistency for manual review
          print("</span><span class="hljs-attr">WARNING:</span> <span class="hljs-string">Data</span> <span class="hljs-string">inconsistency</span> <span class="hljs-string">detected!")</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">handle_inconsistency</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.reconciliation</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">reconcile-dual-write</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>  <span class="hljs-comment"># Async reconciliation</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;</span>
          {{ <span class="hljs-string">outputs.verify_data_consistency.vars.consistent</span> <span class="hljs-string">==</span> <span class="hljs-literal">false</span> }}
</code></pre>
<hr />
<h2 id="heading-pattern-7-sidecar-pattern-for-cross-cutting-concerns"><strong>Pattern 7: Sidecar Pattern for Cross-Cutting Concerns</strong></h2>
<h3 id="heading-implementing-cross-cutting-logic"><strong>Implementing Cross-Cutting Logic</strong></h3>
<p>The sidecar pattern attaches supporting functionality to the main workflow without modifying its core logic.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">main-business-workflow</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.business</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Main</span> <span class="hljs-string">business</span> <span class="hljs-string">logic</span> <span class="hljs-string">with</span> <span class="hljs-string">sidecar</span> <span class="hljs-string">attachments</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">business_logic</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">step1</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
        <span class="hljs-attr">message:</span> <span class="hljs-string">"Business step 1"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">step2</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">"Business step 2 result"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">step3</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
        <span class="hljs-attr">message:</span> <span class="hljs-string">"Business step 3 with result: <span class="hljs-template-variable">{{ outputs.step2.value }}</span>"</span>

<span class="hljs-comment"># Sidecar flow that can be attached to any workflow</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">monitoring-sidecar</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sidecars</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Attachable</span> <span class="hljs-string">monitoring</span> <span class="hljs-string">and</span> <span class="hljs-string">observability</span> <span class="hljs-string">sidecar</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">start_monitoring</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">"MONITORING_START:<span class="hljs-template-variable">{{ parent.execution.id }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">collect_metrics</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"http://metrics-collector.acme.com/api/metrics"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
    <span class="hljs-attr">body:</span> <span class="hljs-string">|
      {
        "execution_id": "{{ parent.execution.id }}",
        "flow_id": "{{ parent.flow.id }}",
        "namespace": "{{ parent.namespace.id }}",
        "start_time": "{{ parent.execution.startDate }}",
        "metrics": {
          "task_count": {{ parent.tasks | length }},
          "inputs": {{ parent.inputs | default({}) | tojson }}
        }
      }
</span>    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">log_performance</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">|
      Sidecar monitoring for {{ parent.execution.id }}
      Flow: {{ parent.flow.id }}
      Tasks monitored: {{ parent.tasks | length }}</span>
</code></pre>
<h3 id="heading-composed-workflow-with-sidecars"><strong>Composed Workflow with Sidecars</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">composed-workflow-with-sidecars</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.composed</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Compose</span> <span class="hljs-string">main</span> <span class="hljs-string">workflow</span> <span class="hljs-string">with</span> <span class="hljs-string">multiple</span> <span class="hljs-string">sidecars</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Attach monitoring sidecar</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">attach_monitoring</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sidecars</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">monitoring-sidecar</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>  <span class="hljs-comment"># Run alongside</span>

  <span class="hljs-comment"># Attach auditing sidecar</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">attach_auditing</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sidecars</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">auditing-sidecar</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>

  <span class="hljs-comment"># Main business logic</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">main_business_process</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.business</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">main-business-workflow</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

  <span class="hljs-comment"># Attach cleanup sidecar (runs after main process)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">attach_cleanup</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sidecars</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">cleanup-sidecar</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;</span>
          {{ <span class="hljs-string">outputs.main_business_process.state.current</span> <span class="hljs-string">in</span> [<span class="hljs-string">'SUCCESS'</span>, <span class="hljs-string">'FAILED'</span>] }}
</code></pre>
<hr />
<h2 id="heading-pattern-8-pipeline-pattern-with-middleware"><strong>Pattern 8: Pipeline Pattern with Middleware</strong></h2>
<h3 id="heading-building-processing-pipelines"><strong>Building Processing Pipelines</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">data-processing-pipeline</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.pipelines</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Configurable</span> <span class="hljs-string">pipeline</span> <span class="hljs-string">with</span> <span class="hljs-string">middleware</span> <span class="hljs-string">support</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">pipeline_stages:</span> [<span class="hljs-string">"validate"</span>, <span class="hljs-string">"transform"</span>, <span class="hljs-string">"enrich"</span>, <span class="hljs-string">"filter"</span>, <span class="hljs-string">"output"</span>]
  <span class="hljs-attr">middleware_enabled:</span> [<span class="hljs-string">"logging"</span>, <span class="hljs-string">"metrics"</span>, <span class="hljs-string">"validation"</span>, <span class="hljs-string">"caching"</span>]

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Pipeline orchestrator</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">pipeline_orchestrator</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_data</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.acme.com/data"</span>
        <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

      <span class="hljs-comment"># Dynamic pipeline stages</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">execute_pipeline_stages</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.pipeline_stages }}</span>"</span>
        <span class="hljs-attr">tasks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"execute_<span class="hljs-template-variable">{{ task.value }}</span>"</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value }}</span>"</span>
            <span class="hljs-attr">cases:</span>
              <span class="hljs-attr">validate:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_validation</span>
                  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
                  <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.pipeline.middleware</span>
                  <span class="hljs-attr">flowId:</span> <span class="hljs-string">validation-middleware</span>
                  <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
                  <span class="hljs-attr">transmit:</span>
                    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">data</span>
                      <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.load_data.uri }}</span>"</span>

              <span class="hljs-attr">transform:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_transformation</span>
                  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
                  <span class="hljs-attr">inputFiles:</span>
                    <span class="hljs-attr">data.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.execute_validation.outputs.validated_data | default(outputs.load_data.uri) }}</span>"</span>
                  <span class="hljs-attr">script:</span> <span class="hljs-string">|
                    import json
                    import pandas as pd
</span>
                    <span class="hljs-string">with</span> <span class="hljs-string">open('data.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
                        <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

                    <span class="hljs-comment"># Transformation logic</span>
                    <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(data)</span>
                    <span class="hljs-comment"># ... transformation steps</span>

                    <span class="hljs-string">df.to_parquet('transformed.parquet',</span> <span class="hljs-string">index=False)</span>
                    <span class="hljs-string">print("Transformation</span> <span class="hljs-string">complete")</span>

              <span class="hljs-attr">enrich:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_enrichment</span>
                  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
                  <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.pipeline.middleware</span>
                  <span class="hljs-attr">flowId:</span> <span class="hljs-string">enrichment-middleware</span>
                  <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

              <span class="hljs-attr">filter:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_filtering</span>
                  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
                  <span class="hljs-attr">script:</span> <span class="hljs-string">|
                    # Filtering logic
                    print("Filtering complete")
</span>
              <span class="hljs-attr">output:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">produce_output</span>
                  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
                  <span class="hljs-attr">format:</span> <span class="hljs-string">"Pipeline execution complete"</span>

          <span class="hljs-comment"># Apply middleware after each stage</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"apply_middleware_<span class="hljs-template-variable">{{ task.value }}</span>"</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
            <span class="hljs-attr">tasks:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"logging_middleware_<span class="hljs-template-variable">{{ task.value }}</span>"</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
                <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.pipeline.middleware</span>
                <span class="hljs-attr">flowId:</span> <span class="hljs-string">logging-middleware</span>
                <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>
                <span class="hljs-attr">transmit:</span>
                  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">stage</span>
                    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value }}</span>"</span>
                  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">execution_id</span>
                    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
                <span class="hljs-attr">conditions:</span>
                  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
                    <span class="hljs-attr">expression:</span> <span class="hljs-string">"'logging' in vars.middleware_enabled"</span>

              <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"metrics_middleware_<span class="hljs-template-variable">{{ task.value }}</span>"</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
                <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.pipeline.middleware</span>
                <span class="hljs-attr">flowId:</span> <span class="hljs-string">metrics-middleware</span>
                <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>
                <span class="hljs-attr">conditions:</span>
                  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
                    <span class="hljs-attr">expression:</span> <span class="hljs-string">"'metrics' in vars.middleware_enabled"</span>
</code></pre>
<h3 id="heading-middleware-implementation"><strong>Middleware Implementation</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">logging-middleware</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.pipeline.middleware</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Logging</span> <span class="hljs-string">middleware</span> <span class="hljs-string">for</span> <span class="hljs-string">pipeline</span> <span class="hljs-string">stages</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">log_stage_execution</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      INSERT INTO pipeline_logs
      (execution_id, stage, status, start_time, end_time, duration_ms)
      VALUES (
        '{{ inputs.execution_id }}',
        '{{ inputs.stage }}',
        '{{ parent.state.current }}',
        '{{ parent.startDate }}',
        '{{ now() }}',
        EXTRACT(EPOCH FROM (NOW() - '{{ parent.startDate }}')) * 1000
      )</span>
</code></pre>
<hr />
<h2 id="heading-pattern-9-bulkhead-pattern-for-fault-isolation"><strong>Pattern 9: Bulkhead Pattern for Fault Isolation</strong></h2>
<h3 id="heading-isolating-failures-with-resource-pools"><strong>Isolating Failures with Resource Pools</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">bulkhead-pattern</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.resilience</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Isolate</span> <span class="hljs-string">failures</span> <span class="hljs-string">using</span> <span class="hljs-string">resource</span> <span class="hljs-string">pools</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-comment"># Different resource pools for different services</span>
  <span class="hljs-attr">resource_pools:</span>
    <span class="hljs-attr">payment_service:</span>
      <span class="hljs-attr">max_concurrent:</span> <span class="hljs-number">5</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT30S</span>
    <span class="hljs-attr">inventory_service:</span>
      <span class="hljs-attr">max_concurrent:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT20S</span>
    <span class="hljs-attr">shipping_service:</span>
      <span class="hljs-attr">max_concurrent:</span> <span class="hljs-number">2</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT60S</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Resource pool manager</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">acquire_resource_slot</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Increment</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"resource_pool:<span class="hljs-template-variable">{{ inputs.service_name }}</span>:active"</span>
    <span class="hljs-attr">defaultValue:</span> <span class="hljs-number">0</span>
    <span class="hljs-attr">ttl:</span> <span class="hljs-string">PT5M</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_pool_capacity</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
    <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
      {{ outputs.acquire_resource_slot.value &lt;= 
         vars.resource_pools[inputs.service_name].max_concurrent }}
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">execute_with_resource_limit</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.service_name }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">payment_service:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_payment_service</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://payments.acme.com/api/process"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_data }}</span>"</span>
          <span class="hljs-attr">timeout:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.resource_pools.payment_service.timeout }}</span>"</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.check_pool_capacity.value }}</span>"</span>

      <span class="hljs-attr">inventory_service:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_inventory_service</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
          <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://inventory.acme.com/api/check"</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.request_data }}</span>"</span>
          <span class="hljs-attr">timeout:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.resource_pools.inventory_service.timeout }}</span>"</span>
          <span class="hljs-attr">conditions:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
              <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.check_pool_capacity.value }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">pool_capacity_exceeded</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">"Resource pool capacity exceeded for <span class="hljs-template-variable">{{ inputs.service_name }}</span>"</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ not outputs.check_pool_capacity.value }}</span>"</span>

  <span class="hljs-comment"># Release resource slot</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">release_resource_slot</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Decrement</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"resource_pool:<span class="hljs-template-variable">{{ inputs.service_name }}</span>:active"</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
          {{ outputs.execute_with_resource_limit != null or
             outputs.pool_capacity_exceeded != null }}</span>
</code></pre>
<hr />
<h2 id="heading-pattern-10-ambassador-pattern-for-service-abstraction"><strong>Pattern 10: Ambassador Pattern for Service Abstraction</strong></h2>
<h3 id="heading-abstracting-external-services"><strong>Abstracting External Services</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">payment-service-ambassador</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.ambassadors</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Ambassador</span> <span class="hljs-string">pattern</span> <span class="hljs-string">for</span> <span class="hljs-string">payment</span> <span class="hljs-string">service</span> <span class="hljs-string">abstraction</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Service discovery and health check</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">discover_payment_endpoints</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://service-discovery.acme.com/api/endpoints/payment"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">GET</span>
    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">select_healthy_endpoint</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">endpoints.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.discover_payment_endpoints.uri }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
      import requests
      from concurrent.futures import ThreadPoolExecutor, as_completed
</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('endpoints.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">endpoints</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

      <span class="hljs-string">def</span> <span class="hljs-string">check_endpoint_health(endpoint):</span>
          <span class="hljs-attr">try:</span>
              <span class="hljs-string">response</span> <span class="hljs-string">=</span> <span class="hljs-string">requests.get(</span>
                  <span class="hljs-string">f"{endpoint['url']}/health",</span>
                  <span class="hljs-string">timeout=2</span>
              <span class="hljs-string">)</span>
              <span class="hljs-string">return</span> {
                  <span class="hljs-attr">'endpoint':</span> <span class="hljs-string">endpoint</span>,
                  <span class="hljs-attr">'healthy':</span> <span class="hljs-string">response.status_code</span> <span class="hljs-string">==</span> <span class="hljs-number">200</span>,
                  <span class="hljs-attr">'response_time':</span> <span class="hljs-string">response.elapsed.total_seconds()</span>
              }
          <span class="hljs-attr">except:</span>
              <span class="hljs-string">return</span> {
                  <span class="hljs-attr">'endpoint':</span> <span class="hljs-string">endpoint</span>,
                  <span class="hljs-attr">'healthy':</span> <span class="hljs-literal">False</span>,
                  <span class="hljs-attr">'response_time':</span> <span class="hljs-string">float('inf')</span>
              }

      <span class="hljs-comment"># Check all endpoints in parallel</span>
      <span class="hljs-string">with</span> <span class="hljs-string">ThreadPoolExecutor(max_workers=5)</span> <span class="hljs-attr">as executor:</span>
          <span class="hljs-string">futures</span> <span class="hljs-string">=</span> [<span class="hljs-string">executor.submit(check_endpoint_health</span>, <span class="hljs-string">ep)</span> <span class="hljs-string">for</span> <span class="hljs-string">ep</span> <span class="hljs-string">in</span> <span class="hljs-string">endpoints</span>]
          <span class="hljs-string">results</span> <span class="hljs-string">=</span> [<span class="hljs-string">f.result()</span> <span class="hljs-string">for</span> <span class="hljs-string">f</span> <span class="hljs-string">in</span> <span class="hljs-string">as_completed(futures)</span>]

      <span class="hljs-comment"># Select the healthiest endpoint</span>
      <span class="hljs-string">healthy_endpoints</span> <span class="hljs-string">=</span> [<span class="hljs-string">r</span> <span class="hljs-string">for</span> <span class="hljs-string">r</span> <span class="hljs-string">in</span> <span class="hljs-string">results</span> <span class="hljs-string">if</span> <span class="hljs-string">r</span>[<span class="hljs-string">'healthy'</span>]]
      <span class="hljs-attr">if healthy_endpoints:</span>
          <span class="hljs-comment"># Choose the fastest healthy endpoint</span>
          <span class="hljs-string">selected</span> <span class="hljs-string">=</span> <span class="hljs-string">min(healthy_endpoints,</span> <span class="hljs-string">key=lambda</span> <span class="hljs-attr">x:</span> <span class="hljs-string">x['response_time'])</span>
          <span class="hljs-string">print(json.dumps(selected['endpoint']))</span>
      <span class="hljs-attr">else:</span>
          <span class="hljs-string">raise</span> <span class="hljs-string">Exception("No</span> <span class="hljs-string">healthy</span> <span class="hljs-string">payment</span> <span class="hljs-string">endpoints</span> <span class="hljs-string">available")</span>

  <span class="hljs-comment"># Circuit breaker and retry logic</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_payment_service_with_retry</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.select_healthy_endpoint.vars.url }}</span>/api/process"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
    <span class="hljs-attr">body:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.payment_data }}</span>"</span>
    <span class="hljs-attr">retry:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
      <span class="hljs-attr">maxAttempt:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">delay:</span> <span class="hljs-string">PT1S</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT10S</span>

  <span class="hljs-comment"># Response transformation</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_payment_response</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">response.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call_payment_service_with_retry.uri }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('response.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">response</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

      <span class="hljs-comment"># Standardize response format</span>
      <span class="hljs-string">standardized</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'success':</span> <span class="hljs-string">response.get('status')</span> <span class="hljs-string">==</span> <span class="hljs-string">'success'</span>,
          <span class="hljs-attr">'transaction_id':</span> <span class="hljs-string">response.get('id')</span>,
          <span class="hljs-attr">'amount':</span> <span class="hljs-string">response.get('amount')</span>,
          <span class="hljs-attr">'currency':</span> <span class="hljs-string">response.get('currency'</span>, <span class="hljs-string">'USD'</span><span class="hljs-string">)</span>,
          <span class="hljs-attr">'timestamp':</span> <span class="hljs-string">response.get('created')</span>,
          <span class="hljs-attr">'provider':</span> <span class="hljs-string">'payment_service'</span>,
          <span class="hljs-attr">'metadata':</span> {
              <span class="hljs-attr">'original_response':</span> <span class="hljs-string">response</span>
          }
      }

      <span class="hljs-string">print(json.dumps(standardized,</span> <span class="hljs-string">indent=2))</span>

  <span class="hljs-comment"># Cache successful responses</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cache_payment_response</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.redis.Put</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"payment:<span class="hljs-template-variable">{{ inputs.payment_data.transaction_id }}</span>"</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_payment_response.vars }}</span>"</span>
    <span class="hljs-attr">ttl:</span> <span class="hljs-string">PT5M</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_payment_response.vars.success == true }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-putting-it-all-together-a-complete-advanced-workflow"><strong>Putting It All Together: A Complete Advanced Workflow</strong></h2>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">advanced-order-processing</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.production</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Complete</span> <span class="hljs-string">advanced</span> <span class="hljs-string">workflow</span> <span class="hljs-string">combining</span> <span class="hljs-string">multiple</span> <span class="hljs-string">patterns</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Pattern 1: Circuit Breaker for external services</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_service_health</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_payment_service</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.resilience</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">circuit-breaker-check</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service_name</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"payment_service"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_inventory_service</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.resilience</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">circuit-breaker-check</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service_name</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"inventory_service"</span>

  <span class="hljs-comment"># Pattern 2: Bulkhead for resource isolation</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_with_bulkheads</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_order_bulkhead</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.resilience</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">bulkhead-processor</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service_name</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"validation"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">request_data</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.order_data }}</span>"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">reserve_inventory_bulkhead</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.resilience</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">bulkhead-processor</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service_name</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"inventory"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">request_data</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.order_data }}</span>"</span>
        <span class="hljs-attr">conditions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
            <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
              {{ outputs.check_service_health.outputs.check_inventory_service.outputs.circuit_state != 'open' }}
</span>
  <span class="hljs-comment"># Pattern 3: Saga pattern for distributed transaction</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">execute_order_saga</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">saga_step1_payment</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.ambassadors</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">payment-service-ambassador</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">payment_data</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.payment_data }}</span>"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">saga_step2_fulfillment</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://fulfillment.acme.com/api/process"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">body:</span> <span class="hljs-string">|
          {
            "order_id": "{{ execution.id }}",
            "items": {{ inputs.order_data.items | tojson }},
            "payment_reference": "{{ outputs.saga_step1_payment.outputs.transaction_id }}"
          }
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">saga_step3_notification</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
        <span class="hljs-attr">tasks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">notify_customer</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.email.EmailSend</span>
            <span class="hljs-attr">to:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.customer_email }}</span>"</span>
            <span class="hljs-attr">subject:</span> <span class="hljs-string">"Order Confirmation #<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
            <span class="hljs-attr">htmlBody:</span> <span class="hljs-string">|
              &lt;h1&gt;Thank you for your order!&lt;/h1&gt;
              &lt;p&gt;Order ID: {{ execution.id }}&lt;/p&gt;
</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">notify_internal</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
            <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_ORDERS_WEBHOOK') }}</span>"</span>
            <span class="hljs-attr">payload:</span> <span class="hljs-string">|
              {
                "text": "New order processed: {{ execution.id }}"
              }
</span>
  <span class="hljs-comment"># Pattern 4: Event sourcing for audit trail</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">record_order_events</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">record_order_created</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
        <span class="hljs-attr">sql:</span> <span class="hljs-string">|
          INSERT INTO order_events
          (event_id, order_id, event_type, event_data, occurred_at)
          VALUES (
            '{{ execution.id }}_created',
            '{{ execution.id }}',
            'ORDER_CREATED',
            '{{ inputs.order_data | tojson }}',
            NOW()
          )
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">record_order_processing</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
        <span class="hljs-attr">sql:</span> <span class="hljs-string">|
          INSERT INTO order_events
          (event_id, order_id, event_type, event_data, occurred_at)
          VALUES (
            '{{ execution.id }}_processing',
            '{{ execution.id }}',
            'ORDER_PROCESSING',
            '{{ outputs.execute_order_saga | tojson }}',
            NOW()
          )
</span>
  <span class="hljs-comment"># Pattern 5: CQRS update for read models</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_read_models</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>  <span class="hljs-comment"># Async updates</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_order_summary</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
        <span class="hljs-attr">sql:</span> <span class="hljs-string">|
          INSERT INTO order_summaries
          (order_id, customer_id, total_amount, status, items_count, created_at)
          VALUES (
            '{{ execution.id }}',
            '{{ inputs.customer_id }}',
            {{ inputs.order_data.total }},
            'processed',
            {{ inputs.order_data.items | length }},
            NOW()
          )
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_customer_metrics</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
        <span class="hljs-attr">sql:</span> <span class="hljs-string">|
          UPDATE customer_metrics
          SET 
            total_orders = total_orders + 1,
            total_spent = total_spent + {{ inputs.order_data.total }},
            last_order_at = NOW()
          WHERE customer_id = '{{ inputs.customer_id }}'
</span>
  <span class="hljs-comment"># Pattern 6: Sidecar for monitoring</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">attach_monitoring_sidecar</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sidecars</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">monitoring-sidecar</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>
</code></pre>
<hr />
<h2 id="heading-best-practices-for-advanced-patterns"><strong>Best Practices for Advanced Patterns</strong></h2>
<h3 id="heading-1-pattern-selection-guide"><strong>1. Pattern Selection Guide</strong></h3>
<ul>
<li><p><strong>For fault tolerance</strong>: Circuit Breaker, Bulkhead, Retry</p>
</li>
<li><p><strong>For distributed transactions</strong>: Saga, Event Sourcing</p>
</li>
<li><p><strong>For migration</strong>: Strangler Fig, Dual Write</p>
</li>
<li><p><strong>For scalability</strong>: CQRS, Sharding, Partitioning</p>
</li>
<li><p><strong>For observability</strong>: Sidecar, Ambassador</p>
</li>
</ul>
<h3 id="heading-2-testing-advanced-patterns"><strong>2. Testing Advanced Patterns</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># Test circuit breaker</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">test-circuit-breaker</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.tests</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">simulate_failures</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ range(1, 6) }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call_failing_service</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"http://mock-service/fail"</span>
        <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">verify_circuit_tripped</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.cache.Get</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"circuit_breaker:mock-service"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">assert_circuit_open</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
    <span class="hljs-attr">expression:</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ outputs.verify_circuit_tripped.value.state == "open" }}</span>'</span>
</code></pre>
<h3 id="heading-3-monitoring-and-observability"><strong>3. Monitoring and Observability</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">id:</span> <span class="hljs-string">pattern-metrics-collector</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.monitoring</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">collect_pattern_metrics</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      # Collect metrics for each pattern
      metrics = {
          'circuit_breaker': {
              'open_circuits': count_open_circuits(),
              'tripped_today': count_trips_today()
          },
          'saga': {
              'active_sagas': count_active_sagas(),
              'compensation_rate': calculate_compensation_rate()
          },
          'cqrs': {
              'read_lag_ms': calculate_read_lag(),
              'write_throughput': calculate_write_throughput()
          }
      }</span>
</code></pre>
<h3 id="heading-4-documentation-strategy"><strong>4. Documentation Strategy</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># Annotate patterns in flow metadata</span>
<span class="hljs-attr">labels:</span>
  <span class="hljs-attr">patterns:</span> <span class="hljs-string">"circuit-breaker,saga,cqrs"</span>
  <span class="hljs-attr">complexity:</span> <span class="hljs-string">"advanced"</span>
  <span class="hljs-attr">team:</span> <span class="hljs-string">"platform-engineering"</span>

<span class="hljs-attr">documentation:</span> <span class="hljs-string">|
  ## Applied Patterns
</span>
  <span class="hljs-comment">### Circuit Breaker</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Prevents</span> <span class="hljs-string">cascading</span> <span class="hljs-string">failures</span> <span class="hljs-string">from</span> <span class="hljs-string">payment</span> <span class="hljs-string">service</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Config:</span> <span class="hljs-number">5</span> <span class="hljs-string">failures</span> <span class="hljs-string">trips</span> <span class="hljs-string">circuit,</span> <span class="hljs-number">5</span> <span class="hljs-string">minute</span> <span class="hljs-string">timeout</span>

  <span class="hljs-comment">### Saga Pattern  </span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Manages</span> <span class="hljs-string">distributed</span> <span class="hljs-string">order</span> <span class="hljs-string">processing</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Compensation:</span> <span class="hljs-string">reverse</span> <span class="hljs-string">payment,</span> <span class="hljs-string">release</span> <span class="hljs-string">inventory</span>

  <span class="hljs-comment">### CQRS</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Separate</span> <span class="hljs-string">read/write</span> <span class="hljs-string">models</span> <span class="hljs-string">for</span> <span class="hljs-string">orders</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Eventual consistency:</span> <span class="hljs-string">&lt;</span> <span class="hljs-number">1</span> <span class="hljs-string">second</span> <span class="hljs-string">lag</span>
</code></pre>
<hr />
<h2 id="heading-common-anti-patterns-to-avoid"><strong>Common Anti-Patterns to Avoid</strong></h2>
<h3 id="heading-1-over-engineering"><strong>1. Over-Engineering</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD: Using saga for simple local transaction</span>
<span class="hljs-comment"># GOOD: Use simple sequential tasks for local operations</span>
</code></pre>
<h3 id="heading-2-pattern-misapplication"><strong>2. Pattern Misapplication</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD: Using event sourcing for simple CRUD</span>
<span class="hljs-comment"># GOOD: Event sourcing for audit-critical domains only</span>
</code></pre>
<h3 id="heading-3-ignoring-consistency-boundaries"><strong>3. Ignoring Consistency Boundaries</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD: Distributed transaction across 10 microservices</span>
<span class="hljs-comment"># GOOD: Saga within bounded context, eventual consistency between</span>
</code></pre>
<h3 id="heading-4-neglecting-monitoring"><strong>4. Neglecting Monitoring</strong></h3>
<pre><code class="lang-plaintext"># BAD: Complex patterns without observability
# GOOD: Comprehensive metrics for each pattern
</code></pre>
<hr />
<h2 id="heading-conclusion-becoming-a-workflow-architect"><strong>Conclusion: Becoming a Workflow Architect</strong></h2>
<p>You've now explored the most powerful patterns in workflow orchestration. Remember:</p>
<ol>
<li><p><strong>Patterns are tools, not goals</strong> - Use them to solve specific problems</p>
</li>
<li><p><strong>Start simple, add complexity gradually</strong> - Don't over-engineer from day one</p>
</li>
<li><p><strong>Monitor everything</strong> - Complex patterns need comprehensive observability</p>
</li>
<li><p><strong>Document decisions</strong> - Future you (and your team) will thank you</p>
</li>
<li><p><strong>Test thoroughly</strong> - Edge cases multiply with complexity</p>
</li>
</ol>
<h3 id="heading-your-advanced-challenge"><strong>Your Advanced Challenge:</strong></h3>
<p>Pick one complex business problem and solve it with multiple patterns:</p>
<ol>
<li><p><strong>E-commerce checkout</strong> with inventory, payment, and fulfillment</p>
</li>
<li><p><strong>Data pipeline</strong> with validation, transformation, and quality checks</p>
</li>
<li><p><strong>User onboarding</strong> with verification, provisioning, and welcome sequence</p>
</li>
<li><p><strong>Report generation</strong> with data collection, processing, and distribution</p>
</li>
<li><p><strong>System migration</strong> from monolith to microservices</p>
</li>
</ol>
<p>Share your architecture in the comments!</p>
<hr />
<p>In the next article, we'll explore <strong>Kestra's Plugin Ecosystem</strong> - how to extend Kestra with custom functionality and integrate with any system.</p>
<p><strong>Before the next article, try:</strong></p>
<ol>
<li><p>Implement one new pattern in an existing workflow</p>
</li>
<li><p>Create a reusable pattern template for your team</p>
</li>
<li><p>Set up monitoring for pattern-specific metrics</p>
</li>
<li><p>Refactor a complex workflow using appropriate patterns</p>
</li>
<li><p>Document your pattern decisions and trade-offs</p>
</li>
</ol>
<p>Remember: Mastery comes from practice, not just knowledge. Build, iterate, and learn from each implementation.</p>
<p>Happy orchestrating! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Article 4: Building Your First ETL Pipeline with Kestra.]]></title><description><![CDATA[From Data Extraction to Loading - A Practical Guide
Introduction: Why ETL Still Matters in the Modern Data Stack
Remember when data engineering was "extract, transform, load"? Some say ETL is dead, replaced by ELT, reverse ETL, and data mesh. But her...]]></description><link>https://techwasti.com/article-4-building-your-first-etl-pipeline-with-kestra</link><guid isPermaLink="true">https://techwasti.com/article-4-building-your-first-etl-pipeline-with-kestra</guid><category><![CDATA[#Kestra]]></category><category><![CDATA[dataengineering]]></category><category><![CDATA[#DataEngineering101 #DataOps #BigData #DataInfrastructure]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><category><![CDATA[sofware engineering]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Tue, 10 Feb 2026 18:30:20 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-from-data-extraction-to-loading-a-practical-guide"><strong>From Data Extraction to Loading - A Practical Guide</strong></h2>
<h2 id="heading-introduction-why-etl-still-matters-in-the-modern-data-stack"><strong>Introduction: Why ETL Still Matters in the Modern Data Stack</strong></h2>
<p>Remember when data engineering was "extract, transform, load"? Some say ETL is dead, replaced by ELT, reverse ETL, and data mesh. But here's the truth: <strong>ETL is more alive than ever</strong>, just smarter. And Kestra makes it accessible to everyone, not just senior data engineers.</p>
<p><strong>In this article, you'll build a complete production-ready ETL pipeline that:</strong></p>
<ul>
<li><p>Extracts data from multiple sources (API, CSV, database)</p>
</li>
<li><p>Transforms with proper error handling and validation</p>
</li>
<li><p>Loads to a data warehouse</p>
</li>
<li><p>Monitors data quality</p>
</li>
<li><p>Sends alerts on failures</p>
</li>
<li><p>Scales to handle millions of records</p>
</li>
</ul>
<p><strong>The Business Problem</strong>: ACME Corp needs daily sales reports from multiple sources. Currently, marketing runs manual Excel reports, finance has SQL queries, and sales uses a legacy system. Your mission: automate everything into a single source of truth.</p>
<hr />
<h2 id="heading-phase-1-blueprint-your-etl-pipeline"><strong>Phase 1: Blueprint Your ETL Pipeline</strong></h2>
<h3 id="heading-understand-your-data-sources"><strong>Understand Your Data Sources</strong></h3>
<p>Before writing a single line of YAML, document your sources:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Source</td><td>Type</td><td>Frequency</td><td>Volume</td><td>Format</td><td>Owner</td></tr>
</thead>
<tbody>
<tr>
<td>Stripe API</td><td>Payment processor</td><td>Daily</td><td>10K records</td><td>JSON</td><td>Finance</td></tr>
<tr>
<td>Shopify</td><td>E-commerce</td><td>Hourly</td><td>50K records</td><td>CSV</td><td>Marketing</td></tr>
<tr>
<td>PostgreSQL</td><td>Internal DB</td><td>Daily</td><td>100K records</td><td>SQL</td><td>Sales</td></tr>
<tr>
<td>Google Sheets</td><td>Manual input</td><td>Weekly</td><td>500 records</td><td>CSV</td><td>Ops</td></tr>
</tbody>
</table>
</div><h3 id="heading-design-your-target-schema"><strong>Design Your Target Schema</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-string">--</span> <span class="hljs-attr">Target:</span> <span class="hljs-string">Data</span> <span class="hljs-string">warehouse</span> <span class="hljs-string">(Snowflake/BigQuery/PostgreSQL)</span>
<span class="hljs-string">CREATE</span> <span class="hljs-string">TABLE</span> <span class="hljs-string">sales_fact</span> <span class="hljs-string">(</span>
    <span class="hljs-string">sale_id</span> <span class="hljs-string">VARCHAR(100)</span> <span class="hljs-string">PRIMARY</span> <span class="hljs-string">KEY,</span>
    <span class="hljs-string">transaction_date</span> <span class="hljs-string">DATE,</span>
    <span class="hljs-string">customer_id</span> <span class="hljs-string">VARCHAR(50),</span>
    <span class="hljs-string">product_id</span> <span class="hljs-string">VARCHAR(50),</span>
    <span class="hljs-string">quantity</span> <span class="hljs-string">INTEGER,</span>
    <span class="hljs-string">unit_price</span> <span class="hljs-string">DECIMAL(10,2),</span>
    <span class="hljs-string">total_amount</span> <span class="hljs-string">DECIMAL(10,2),</span>
    <span class="hljs-string">payment_method</span> <span class="hljs-string">VARCHAR(50),</span>
    <span class="hljs-string">region</span> <span class="hljs-string">VARCHAR(100),</span>
    <span class="hljs-string">sales_rep</span> <span class="hljs-string">VARCHAR(100),</span>
    <span class="hljs-string">source_system</span> <span class="hljs-string">VARCHAR(50),</span>
    <span class="hljs-string">loaded_at</span> <span class="hljs-string">TIMESTAMP,</span>
    <span class="hljs-string">batch_id</span> <span class="hljs-string">VARCHAR(100)</span>
<span class="hljs-string">);</span>

<span class="hljs-string">CREATE</span> <span class="hljs-string">TABLE</span> <span class="hljs-string">customer_dim</span> <span class="hljs-string">(</span>
    <span class="hljs-string">customer_id</span> <span class="hljs-string">VARCHAR(50)</span> <span class="hljs-string">PRIMARY</span> <span class="hljs-string">KEY,</span>
    <span class="hljs-string">customer_name</span> <span class="hljs-string">VARCHAR(255),</span>
    <span class="hljs-string">email</span> <span class="hljs-string">VARCHAR(255),</span>
    <span class="hljs-string">join_date</span> <span class="hljs-string">DATE,</span>
    <span class="hljs-string">customer_tier</span> <span class="hljs-string">VARCHAR(50),</span>
    <span class="hljs-string">lifetime_value</span> <span class="hljs-string">DECIMAL(10,2),</span>
    <span class="hljs-string">last_updated</span> <span class="hljs-string">TIMESTAMP</span>
<span class="hljs-string">);</span>
</code></pre>
<h3 id="heading-define-success-criteria"><strong>Define Success Criteria</strong></h3>
<p>Your pipeline must:</p>
<ol>
<li><p>Process all data within 2 hours</p>
</li>
<li><p>Maintain 99.9% data accuracy</p>
</li>
<li><p>Handle missing data gracefully</p>
</li>
<li><p>Send alerts on failures within 15 minutes</p>
</li>
<li><p>Support backfills for 90 days</p>
</li>
</ol>
<hr />
<h2 id="heading-phase-2-setting-up-your-development-environment"><strong>Phase 2: Setting Up Your Development Environment</strong></h2>
<h3 id="heading-project-structure"><strong>Project Structure</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># Create project structure</span>
<span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">etl-pipeline/{flows,scripts,config,tests}</span>
<span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">etl-pipeline/flows/{extract,transform,load,monitor}</span>
<span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">etl-pipeline/scripts/{python,sql}</span>
<span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">etl-pipeline/config/{dev,prod}</span>
<span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">etl-pipeline/tests/{unit,integration}</span>

<span class="hljs-comment"># Initialize git</span>
<span class="hljs-string">cd</span> <span class="hljs-string">etl-pipeline</span>
<span class="hljs-string">git</span> <span class="hljs-string">init</span>
<span class="hljs-string">echo</span> <span class="hljs-string">"# ACME Sales ETL Pipeline"</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">README.md</span>
</code></pre>
<h3 id="heading-configuration-files"><strong>Configuration Files</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># config/dev/kestra.yml</span>
<span class="hljs-attr">kestra:</span>
  <span class="hljs-attr">server:</span>
    <span class="hljs-attr">basic-auth:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">username:</span> <span class="hljs-string">admin</span>
      <span class="hljs-attr">password:</span> <span class="hljs-string">${DEV_PASSWORD:?}</span>

  <span class="hljs-attr">repository:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">postgres:</span>
      <span class="hljs-attr">host:</span> <span class="hljs-string">localhost</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
      <span class="hljs-attr">database:</span> <span class="hljs-string">kestra_dev</span>

  <span class="hljs-attr">storage:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">local</span>
    <span class="hljs-attr">local:</span>
      <span class="hljs-attr">base-path:</span> <span class="hljs-string">./storage</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">dev</span>
  <span class="hljs-attr">api_timeout:</span> <span class="hljs-string">PT5M</span>
  <span class="hljs-attr">batch_size:</span> <span class="hljs-number">10000</span>
  <span class="hljs-attr">alert_email:</span> <span class="hljs-string">dev-team@acme.com</span>

<span class="hljs-attr">secrets:</span>
  <span class="hljs-attr">stripe_api_key:</span> <span class="hljs-string">${STRIPE_API_KEY}</span>
  <span class="hljs-attr">shopify_token:</span> <span class="hljs-string">${SHOPIFY_TOKEN}</span>
  <span class="hljs-attr">db_password:</span> <span class="hljs-string">${DB_PASSWORD}</span>
</code></pre>
<pre><code class="lang-yaml"><span class="hljs-comment"># config/prod/kestra.yml  </span>
<span class="hljs-attr">kestra:</span>
  <span class="hljs-attr">server:</span>
    <span class="hljs-attr">basic-auth:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">username:</span> <span class="hljs-string">${ADMIN_USER}</span>
      <span class="hljs-attr">password:</span> <span class="hljs-string">${ADMIN_PASSWORD}</span>

  <span class="hljs-attr">repository:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">postgres:</span>
      <span class="hljs-attr">host:</span> <span class="hljs-string">prod-db.acme.com</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
      <span class="hljs-attr">database:</span> <span class="hljs-string">kestra_prod</span>

  <span class="hljs-attr">storage:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">s3</span>
    <span class="hljs-attr">s3:</span>
      <span class="hljs-attr">bucket:</span> <span class="hljs-string">acme-kestra-prod</span>
      <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">production</span>
  <span class="hljs-attr">api_timeout:</span> <span class="hljs-string">PT10M</span>
  <span class="hljs-attr">batch_size:</span> <span class="hljs-number">50000</span>
  <span class="hljs-attr">alert_email:</span> <span class="hljs-string">data-eng@acme.com</span>
  <span class="hljs-attr">slack_channel:</span> <span class="hljs-string">"#data-alerts"</span>

<span class="hljs-attr">secrets:</span>
  <span class="hljs-attr">stripe_api_key:</span> <span class="hljs-string">${STRIPE_API_KEY}</span>
  <span class="hljs-attr">shopify_token:</span> <span class="hljs-string">${SHOPIFY_TOKEN}</span>
  <span class="hljs-attr">db_password:</span> <span class="hljs-string">${DB_PASSWORD_PROD}</span>
  <span class="hljs-attr">snowflake_password:</span> <span class="hljs-string">${SNOWFLAKE_PASSWORD}</span>
</code></pre>
<h3 id="heading-environment-variables"><strong>Environment Variables</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># .env.dev</span>
<span class="hljs-string">DEV_PASSWORD=dev_password_123</span>
<span class="hljs-string">STRIPE_API_KEY=sk_test_abc123</span>
<span class="hljs-string">SHOPIFY_TOKEN=shpat_xyz789</span>
<span class="hljs-string">DB_PASSWORD=postgres_pass</span>
<span class="hljs-string">SNOWFLAKE_PASSWORD=snowflake_dev_pass</span>

<span class="hljs-comment"># .env.prod (keep secret!)</span>
<span class="hljs-string">ADMIN_USER=admin</span>
<span class="hljs-string">ADMIN_PASSWORD=super_secure_prod_pass_456</span>
<span class="hljs-string">STRIPE_API_KEY=sk_live_xyz789</span>
<span class="hljs-string">SHOPIFY_TOKEN=shpat_live_abc123</span>
<span class="hljs-string">DB_PASSWORD_PROD=prod_super_secure_pass</span>
<span class="hljs-string">SNOWFLAKE_PASSWORD=snowflake_prod_secure_pass</span>
</code></pre>
<h3 id="heading-docker-compose-for-development"><strong>Docker Compose for Development</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># docker-compose.yml</span>
<span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">kestra:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">kestra/kestra:latest</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8080:8080"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">KESTRA_CONFIGURATION=file:///app/config/dev/kestra.yml</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./flows:/app/flows</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./scripts:/app/scripts</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config/dev:/app/config</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./storage:/app/storage</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./logs:/app/logs</span>
    <span class="hljs-attr">env_file:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.env.dev</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgres</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">minio</span>

  <span class="hljs-attr">postgres:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:15-alpine</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">kestra_dev</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">kestra</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">${DB_PASSWORD}</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgres_data:/var/lib/postgresql/data</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./init-db:/docker-entrypoint-initdb.d</span>

  <span class="hljs-attr">minio:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">minio/minio</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">server</span> <span class="hljs-string">/data</span> <span class="hljs-string">--console-address</span> <span class="hljs-string">":9090"</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9000:9000"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9090:9090"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MINIO_ROOT_USER:</span> <span class="hljs-string">minioadmin</span>
      <span class="hljs-attr">MINIO_ROOT_PASSWORD:</span> <span class="hljs-string">minioadmin</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">minio_data:/data</span>

  <span class="hljs-comment"># Optional: Add test data sources</span>
  <span class="hljs-attr">mock-stripe:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">stoplight/prism:4</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">mock</span> <span class="hljs-string">-h</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span> <span class="hljs-string">https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4010:4010"</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">postgres_data:</span>
  <span class="hljs-attr">minio_data:</span>
</code></pre>
<hr />
<h2 id="heading-phase-3-building-the-extract-layer"><strong>Phase 3: Building the Extract Layer</strong></h2>
<h3 id="heading-source-1-stripe-payments-api"><strong>Source 1: Stripe Payments API</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/extract/stripe-payments.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">extract-stripe-payments</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
<span class="hljs-attr">revision:</span> <span class="hljs-number">1</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">|
  Extract payment data from Stripe API.
</span>
  <span class="hljs-comment">## Data Characteristics</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Daily volume:</span> <span class="hljs-string">~10,000</span> <span class="hljs-string">transactions</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Historical</span> <span class="hljs-string">data</span> <span class="hljs-string">available</span> <span class="hljs-string">for</span> <span class="hljs-number">90</span> <span class="hljs-string">days</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Includes:</span> <span class="hljs-string">charges,</span> <span class="hljs-string">customers,</span> <span class="hljs-string">refunds</span>

  <span class="hljs-comment">## Rate Limits</span>
  <span class="hljs-bullet">-</span> <span class="hljs-number">100</span> <span class="hljs-string">requests</span> <span class="hljs-string">per</span> <span class="hljs-string">second</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Paginated</span> <span class="hljs-string">responses</span> <span class="hljs-string">(limit</span> <span class="hljs-number">100</span> <span class="hljs-string">per</span> <span class="hljs-string">page)</span>

<span class="hljs-attr">labels:</span>
  <span class="hljs-attr">source:</span> <span class="hljs-string">stripe</span>
  <span class="hljs-attr">data-type:</span> <span class="hljs-string">transactions</span>
  <span class="hljs-attr">pii:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">compliance:</span> <span class="hljs-string">pci-dss</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">stripe_base_url:</span> <span class="hljs-string">"https://api.stripe.com/v1"</span>
  <span class="hljs-attr">default_limit:</span> <span class="hljs-number">100</span>
  <span class="hljs-attr">max_pages:</span> <span class="hljs-number">1000</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">start_date</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Start date for data extraction"</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() | dateAdd(-1, 'DAYS') | date('yyyy-MM-dd') }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">end_date</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"End date for data extraction"</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() | date('yyyy-MM-dd') }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">data_types</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">ARRAY</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Types of data to extract"</span>
    <span class="hljs-attr">defaults:</span> [<span class="hljs-string">"charges"</span>, <span class="hljs-string">"customers"</span>, <span class="hljs-string">"refunds"</span>, <span class="hljs-string">"payment_intents"</span>]

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Task 1: Validate inputs</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_dates</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
    <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
      {{ inputs.start_date | date('yyyy-MM-dd') &lt;= 
         inputs.end_date | date('yyyy-MM-dd') }}
</span>    <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Start date must be before or equal to end date"</span>

  <span class="hljs-comment"># Task 2: Calculate date range</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_date_range</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">&gt;
      {{ range(inputs.start_date | date('yyyy-MM-dd'), 
               inputs.end_date | date('yyyy-MM-dd'), 
               'P1D') | tojson }}
</span>
  <span class="hljs-comment"># Task 3: Extract each data type in parallel</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_data_types</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachParallel</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.data_types }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"extract_<span class="hljs-template-variable">{{ task.value }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">"Extract <span class="hljs-template-variable">{{ task.value }}</span> from Stripe"</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.stripe_base_url }}</span>/<span class="hljs-template-variable">{{ task.value }}</span>"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">GET</span>
        <span class="hljs-attr">headers:</span>
          <span class="hljs-attr">Authorization:</span> <span class="hljs-string">"Bearer <span class="hljs-template-variable">{{ secret('STRIPE_API_KEY') }}</span>"</span>
        <span class="hljs-attr">queryParams:</span>
          <span class="hljs-attr">limit:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.default_limit }}</span>"</span>
          <span class="hljs-string">created[gte]:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.start_date | date('timestamp') }}</span>"</span>
          <span class="hljs-string">created[lte]:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.end_date | date('timestamp') }}</span>"</span>

        <span class="hljs-comment"># Pagination handling</span>
        <span class="hljs-attr">pagination:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">next_page</span>
          <span class="hljs-attr">results:</span> <span class="hljs-string">body.data</span>
          <span class="hljs-attr">nextPageToken:</span> <span class="hljs-string">body.has_more</span>
          <span class="hljs-attr">nextPageTokenPath:</span> <span class="hljs-string">body.has_more</span>
          <span class="hljs-attr">nextPageCursorPath:</span> <span class="hljs-string">body.data[-1].id</span>
          <span class="hljs-attr">nextPageQueryParams:</span>
            <span class="hljs-attr">starting_after:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ nextPageToken }}</span>"</span>

        <span class="hljs-comment"># Rate limiting</span>
        <span class="hljs-attr">rateLimit:</span>
          <span class="hljs-attr">requestsPerSecond:</span> <span class="hljs-number">10</span>

        <span class="hljs-comment"># Error handling</span>
        <span class="hljs-attr">retry:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
          <span class="hljs-attr">maxAttempt:</span> <span class="hljs-number">5</span>
          <span class="hljs-attr">delay:</span> <span class="hljs-string">PT10S</span>

        <span class="hljs-comment"># Store results</span>
        <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">outputFile:</span> <span class="hljs-string">"stripe_<span class="hljs-template-variable">{{ task.value }}</span>_<span class="hljs-template-variable">{{ execution.startDate | date('yyyyMMddHHmmss') }}</span>.json"</span>

  <span class="hljs-comment"># Task 4: Merge and validate extracted data</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">merge_stripe_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Merge and validate Stripe data"</span>
    <span class="hljs-attr">inputFiles:</span>
      {<span class="hljs-string">%</span> <span class="hljs-string">for</span> <span class="hljs-string">data_type</span> <span class="hljs-string">in</span> <span class="hljs-string">inputs.data_types</span> <span class="hljs-string">%</span>}
      <span class="hljs-string">stripe_{{</span> <span class="hljs-string">data_type</span> <span class="hljs-string">}}.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.extract_data_types.outputs['extract_' + data_type].uri }}</span>"</span>
      {<span class="hljs-string">%</span> <span class="hljs-string">endfor</span> <span class="hljs-string">%</span>}
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
      import pandas as pd
      from datetime import datetime
</span>
      <span class="hljs-comment"># Load all extracted data</span>
      <span class="hljs-string">all_data</span> <span class="hljs-string">=</span> {}
      <span class="hljs-string">for</span> <span class="hljs-string">data_type</span> <span class="hljs-string">in</span> {{ <span class="hljs-string">inputs.data_types</span> <span class="hljs-string">|</span> <span class="hljs-string">tojson</span> }}<span class="hljs-string">:</span>
          <span class="hljs-string">with</span> <span class="hljs-string">open(f'stripe_{data_type}.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
              <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>
              <span class="hljs-string">all_data[data_type]</span> <span class="hljs-string">=</span> <span class="hljs-string">data['data']</span>

      <span class="hljs-comment"># Validate data</span>
      <span class="hljs-string">validation_results</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'extraction_time':</span> <span class="hljs-string">datetime.now().isoformat()</span>,
          <span class="hljs-attr">'total_records':</span> <span class="hljs-string">sum(len(data)</span> <span class="hljs-string">for</span> <span class="hljs-string">data</span> <span class="hljs-string">in</span> <span class="hljs-string">all_data.values())</span>,
          <span class="hljs-attr">'record_counts':</span> {<span class="hljs-attr">k:</span> <span class="hljs-string">len(v)</span> <span class="hljs-string">for</span> <span class="hljs-string">k</span>, <span class="hljs-string">v</span> <span class="hljs-string">in</span> <span class="hljs-string">all_data.items()</span>},
          <span class="hljs-attr">'date_range':</span> {
              <span class="hljs-attr">'start':</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.start_date }}</span>'</span>,
              <span class="hljs-attr">'end':</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.end_date }}</span>'</span>
          }
      }

      <span class="hljs-comment"># Check for missing required fields</span>
      <span class="hljs-string">required_fields</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'charges':</span> [<span class="hljs-string">'id'</span>, <span class="hljs-string">'amount'</span>, <span class="hljs-string">'currency'</span>, <span class="hljs-string">'customer'</span>],
          <span class="hljs-attr">'customers':</span> [<span class="hljs-string">'id'</span>, <span class="hljs-string">'email'</span>, <span class="hljs-string">'created'</span>],
          <span class="hljs-attr">'refunds':</span> [<span class="hljs-string">'id'</span>, <span class="hljs-string">'charge'</span>, <span class="hljs-string">'amount'</span>]
      }

      <span class="hljs-string">validation_results['missing_fields']</span> <span class="hljs-string">=</span> {}
      <span class="hljs-string">for</span> <span class="hljs-string">data_type,</span> <span class="hljs-string">records</span> <span class="hljs-string">in</span> <span class="hljs-string">all_data.items():</span>
          <span class="hljs-attr">if data_type in required_fields:</span>
              <span class="hljs-string">for</span> <span class="hljs-string">record</span> <span class="hljs-string">in</span> <span class="hljs-string">records[:10]:</span>  <span class="hljs-comment"># Sample check</span>
                  <span class="hljs-string">missing</span> <span class="hljs-string">=</span> [<span class="hljs-string">field</span> <span class="hljs-string">for</span> <span class="hljs-string">field</span> <span class="hljs-string">in</span> <span class="hljs-string">required_fields</span>[<span class="hljs-string">data_type</span>] 
                            <span class="hljs-string">if</span> <span class="hljs-string">field</span> <span class="hljs-string">not</span> <span class="hljs-string">in</span> <span class="hljs-string">record</span>]
                  <span class="hljs-attr">if missing:</span>
                      <span class="hljs-string">validation_results['missing_fields'].setdefault(data_type,</span> []<span class="hljs-string">).append(missing)</span>

      <span class="hljs-comment"># Save merged data</span>
      <span class="hljs-string">merged_data</span> <span class="hljs-string">=</span> {}
      <span class="hljs-string">for</span> <span class="hljs-string">data_type,</span> <span class="hljs-string">records</span> <span class="hljs-string">in</span> <span class="hljs-string">all_data.items():</span>
          <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(records)</span>
          <span class="hljs-string">df.to_parquet(f'stripe_{data_type}_merged.parquet',</span> <span class="hljs-string">index=False)</span>
          <span class="hljs-string">merged_data[data_type]</span> <span class="hljs-string">=</span> <span class="hljs-string">f'stripe_{data_type}_merged.parquet'</span>

      <span class="hljs-comment"># Save validation results</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('validation_report.json',</span> <span class="hljs-string">'w'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">json.dump(validation_results,</span> <span class="hljs-string">f,</span> <span class="hljs-string">indent=2)</span>

      <span class="hljs-comment"># Output to Kestra</span>
      <span class="hljs-string">print(json.dumps({</span>
          <span class="hljs-attr">'validation':</span> <span class="hljs-string">validation_results,</span>
          <span class="hljs-attr">'files':</span> <span class="hljs-string">merged_data</span>
      <span class="hljs-string">}))</span>

    <span class="hljs-attr">taskRunner:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">2Gi</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">python:3.11-slim</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">extract_data_types</span>

  <span class="hljs-comment"># Task 5: Send extraction summary</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_extraction_summary</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_WEBHOOK_URL') }}</span>"</span>
    <span class="hljs-attr">payload:</span> <span class="hljs-string">|
      {
        "blocks": [
          {
            "type": "header",
            "text": {
              "type": "plain_text",
              "text": "📥 Stripe Data Extraction Complete"
            }
          },
          {
            "type": "section",
            "fields": [
              {
                "type": "mrkdwn",
                "text": "*Date Range:*\n{{ inputs.start_date }} to {{ inputs.end_date }}"
              },
              {
                "type": "mrkdwn",
                "text": "*Total Records:*\n{{ outputs.merge_stripe_data.vars.validation.total_records }}"
              }
            ]
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "{% for data_type, count in outputs.merge_stripe_data.vars.validation.record_counts.items() %}• *{{ data_type }}*: {{ count }} records\n{% endfor %}"
            }
          }
        ]
      }
</span>
    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">merge_stripe_data</span>

<span class="hljs-attr">triggers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">daily_extraction</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Schedule</span>
    <span class="hljs-attr">cron:</span> <span class="hljs-string">"0 1 * * *"</span>  <span class="hljs-comment"># 1 AM daily</span>
    <span class="hljs-attr">timezone:</span> <span class="hljs-string">UTC</span>
    <span class="hljs-attr">inputs:</span>
      <span class="hljs-attr">start_date:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() | dateAdd(-1, 'DAYS') | date('yyyy-MM-dd') }}</span>"</span>
      <span class="hljs-attr">end_date:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() | date('yyyy-MM-dd') }}</span>"</span>

<span class="hljs-attr">timeout:</span> <span class="hljs-string">PT1H</span>
</code></pre>
<h3 id="heading-source-2-shopify-e-commerce-data"><strong>Source 2: Shopify E-commerce Data</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/extract/shopify-orders.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">extract-shopify-orders</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Extract</span> <span class="hljs-string">order</span> <span class="hljs-string">data</span> <span class="hljs-string">from</span> <span class="hljs-string">Shopify</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">get_order_ids</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://<span class="hljs-template-variable">{{ secret('SHOPIFY_STORE') }}</span>.myshopify.com/admin/api/2024-01/orders.json"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">GET</span>
    <span class="hljs-attr">headers:</span>
      <span class="hljs-attr">X-Shopify-Access-Token:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SHOPIFY_TOKEN') }}</span>"</span>
    <span class="hljs-attr">queryParams:</span>
      <span class="hljs-attr">status:</span> <span class="hljs-string">any</span>
      <span class="hljs-attr">created_at_min:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.startDate | dateAdd(-1, 'DAYS') | date('yyyy-MM-dd') }}</span>T00:00:00Z"</span>
      <span class="hljs-attr">limit:</span> <span class="hljs-number">250</span>
    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">pagination:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">header_link</span>
      <span class="hljs-attr">maxPage:</span> <span class="hljs-number">100</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">enrich_orders</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachParallel</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.get_order_ids.body.orders | map('id') | list }}</span>"</span>
    <span class="hljs-attr">maxParallel:</span> <span class="hljs-number">5</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"get_order_<span class="hljs-template-variable">{{ task.value }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://<span class="hljs-template-variable">{{ secret('SHOPIFY_STORE') }}</span>.myshopify.com/admin/api/2024-01/orders/<span class="hljs-template-variable">{{ task.value }}</span>.json"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">GET</span>
        <span class="hljs-attr">headers:</span>
          <span class="hljs-attr">X-Shopify-Access-Token:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SHOPIFY_TOKEN') }}</span>"</span>
        <span class="hljs-attr">queryParams:</span>
          <span class="hljs-attr">fields:</span> <span class="hljs-string">"id,email,created_at,financial_status,total_price,line_items,customer"</span>
        <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">outputFile:</span> <span class="hljs-string">"shopify_order_<span class="hljs-template-variable">{{ task.value }}</span>.json"</span>
</code></pre>
<h3 id="heading-source-3-internal-postgresql-database"><strong>Source 3: Internal PostgreSQL Database</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/extract/internal-sales.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">extract-internal-sales</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Extract</span> <span class="hljs-string">sales</span> <span class="hljs-string">data</span> <span class="hljs-string">from</span> <span class="hljs-string">internal</span> <span class="hljs-string">database</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_sales_transactions</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"jdbc:postgresql://<span class="hljs-template-variable">{{ secret('DB_HOST') }}</span>:5432/<span class="hljs-template-variable">{{ secret('DB_NAME') }}</span>"</span>
    <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('DB_USER') }}</span>"</span>
    <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('DB_PASSWORD') }}</span>"</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      WITH daily_sales AS (
        SELECT 
          t.transaction_id,
          t.transaction_date,
          c.customer_id,
          c.customer_name,
          p.product_id,
          p.product_name,
          t.quantity,
          t.unit_price,
          t.total_amount,
          t.sales_rep_id,
          sr.region,
          'internal' as source_system,
          CURRENT_TIMESTAMP as extracted_at
        FROM transactions t
        JOIN customers c ON t.customer_id = c.customer_id
        JOIN products p ON t.product_id = p.product_id
        JOIN sales_reps sr ON t.sales_rep_id = sr.rep_id
        WHERE t.transaction_date &gt;= '{{ execution.startDate | dateAdd(-1, 'DAYS') | date('yyyy-MM-dd') }}'
          AND t.transaction_date &lt; '{{ execution.startDate | date('yyyy-MM-dd') }}'
          AND t.status = 'completed'
      )
      SELECT * FROM daily_sales
</span>    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">fetchSize:</span> <span class="hljs-number">10000</span>
    <span class="hljs-attr">chunkSize:</span> <span class="hljs-number">5000</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">save_to_parquet</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">sales_data.json:</span> <span class="hljs-string">|
        {{ outputs.extract_sales_transactions.rows | tojson }}
</span>    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import json
</span>
      <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">json.loads('sales_data.json')</span>
      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(data)</span>

      <span class="hljs-comment"># Convert date columns</span>
      <span class="hljs-string">date_columns</span> <span class="hljs-string">=</span> [<span class="hljs-string">'transaction_date'</span>, <span class="hljs-string">'extracted_at'</span>]
      <span class="hljs-attr">for col in date_columns:</span>
          <span class="hljs-attr">if col in df.columns:</span>
              <span class="hljs-string">df[col]</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.to_datetime(df[col])</span>

      <span class="hljs-comment"># Save to parquet</span>
      <span class="hljs-string">df.to_parquet('internal_sales.parquet',</span> <span class="hljs-string">index=False)</span>
      <span class="hljs-string">print(f"Saved</span> {<span class="hljs-string">len(df)</span>} <span class="hljs-string">records</span> <span class="hljs-string">to</span> <span class="hljs-string">internal_sales.parquet")</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">extract_sales_transactions</span>
</code></pre>
<h3 id="heading-source-4-google-sheets-manual-input"><strong>Source 4: Google Sheets (Manual Input)</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/extract/google-sheets.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">extract-google-sheets</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Extract</span> <span class="hljs-string">manual</span> <span class="hljs-string">inputs</span> <span class="hljs-string">from</span> <span class="hljs-string">Google</span> <span class="hljs-string">Sheets</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">authenticate_google</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.gcp.Oauth</span>
    <span class="hljs-attr">scopes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">https://www.googleapis.com/auth/spreadsheets.readonly</span>
    <span class="hljs-attr">credentials:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('GOOGLE_CREDENTIALS_JSON') }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_sheet_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.gcp.sheets.Read</span>
    <span class="hljs-attr">spreadsheetId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('GOOGLE_SHEET_ID') }}</span>"</span>
    <span class="hljs-attr">range:</span> <span class="hljs-string">"SalesData!A:Z"</span>
    <span class="hljs-attr">store:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">convert_to_structured</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">sheet_data.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.extract_sheet_data.uri }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import json
</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('sheet_data.json',</span> <span class="hljs-string">'r'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">json.load(f)</span>

      <span class="hljs-comment"># Assuming first row is headers</span>
      <span class="hljs-string">rows</span> <span class="hljs-string">=</span> <span class="hljs-string">data['values']</span>
      <span class="hljs-string">if</span> <span class="hljs-string">len(rows)</span> <span class="hljs-string">&lt;</span> <span class="hljs-attr">2:</span>
          <span class="hljs-string">raise</span> <span class="hljs-string">ValueError("No</span> <span class="hljs-string">data</span> <span class="hljs-string">in</span> <span class="hljs-string">sheet")</span>

      <span class="hljs-string">headers</span> <span class="hljs-string">=</span> <span class="hljs-string">rows[0]</span>
      <span class="hljs-string">data_rows</span> <span class="hljs-string">=</span> <span class="hljs-string">rows[1:]</span>

      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(data_rows,</span> <span class="hljs-string">columns=headers)</span>

      <span class="hljs-comment"># Clean up data</span>
      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">df.dropna(how='all')</span>  <span class="hljs-comment"># Remove empty rows</span>

      <span class="hljs-comment"># Standardize column names</span>
      <span class="hljs-string">df.columns</span> <span class="hljs-string">=</span> <span class="hljs-string">df.columns.str.lower().str.replace('</span> <span class="hljs-string">', '</span><span class="hljs-string">_')</span>

      <span class="hljs-comment"># Save</span>
      <span class="hljs-string">df.to_parquet('manual_sales.parquet',</span> <span class="hljs-string">index=False)</span>
      <span class="hljs-string">print(f"Extracted</span> {<span class="hljs-string">len(df)</span>} <span class="hljs-string">manual</span> <span class="hljs-string">entries")</span>
</code></pre>
<hr />
<h2 id="heading-phase-4-building-the-transform-layer"><strong>Phase 4: Building the Transform Layer</strong></h2>
<h3 id="heading-transformation-pipeline-architecture"><strong>Transformation Pipeline Architecture</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/transform/master-transformer.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">master-transformer</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Orchestrate</span> <span class="hljs-string">all</span> <span class="hljs-string">data</span> <span class="hljs-string">transformations</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Parallel transformation of all sources</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_all_sources</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_stripe</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">transform-stripe-payments</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">input_files</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ parent.outputs.extract_stripe_payments.outputs.merge_stripe_data.vars.files }}</span>"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_shopify</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">transform-shopify-orders</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_internal</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>  
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">transform-internal-sales</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_manual</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">transform-google-sheets</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

  <span class="hljs-comment"># Deduplicate across sources</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">deduplicate_records</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">deduplicate-sales</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">stripe_data</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_all_sources.outputs.transform_stripe.outputs.transformed_files }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shopify_data</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_all_sources.outputs.transform_shopify.outputs.transformed_files }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">internal_data</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_all_sources.outputs.transform_internal.outputs.transformed_files }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">manual_data</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_all_sources.outputs.transform_manual.outputs.transformed_files }}</span>"</span>

  <span class="hljs-comment"># Enrich with customer data</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">enrich_customer_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">enrich-customer-dimension</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">sales_data</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.deduplicate_records.outputs.deduplicated_file }}</span>"</span>

  <span class="hljs-comment"># Calculate metrics</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">calculate_metrics</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">calculate-sales-metrics</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">enriched_data</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.enrich_customer_data.outputs.enriched_file }}</span>"</span>

  <span class="hljs-comment"># Generate data quality report</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">data_quality_check</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.quality</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">run-data-quality-checks</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">transformed_data</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.calculate_metrics.outputs.final_data }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">validation_rules</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">|
          {
            "rules": [
              {"field": "total_amount", "type": "not_null"},
              {"field": "customer_id", "type": "not_null"},
              {"field": "transaction_date", "type": "not_null"},
              {"field": "total_amount", "type": "positive"},
              {"field": "quantity", "type": "positive_integer"}
            ]
          }</span>
</code></pre>
<h3 id="heading-stripe-transformation-flow"><strong>Stripe Transformation Flow</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/transform/transform-stripe-payments.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">transform-stripe-payments</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Transform</span> <span class="hljs-string">raw</span> <span class="hljs-string">Stripe</span> <span class="hljs-string">data</span> <span class="hljs-string">into</span> <span class="hljs-string">standardized</span> <span class="hljs-string">format</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">input_files</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">JSON</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Map of data type to file path"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">transformation_date</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() }}</span>"</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_and_transform</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Transform Stripe payments data"</span>
    <span class="hljs-attr">inputFiles:</span>
      {<span class="hljs-string">%</span> <span class="hljs-string">for</span> <span class="hljs-string">data_type</span>, <span class="hljs-string">file_path</span> <span class="hljs-string">in</span> <span class="hljs-string">inputs.input_files.items()</span> <span class="hljs-string">%</span>}
      {{ <span class="hljs-string">data_type</span> }}<span class="hljs-string">.parquet:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ file_path }}</span>"</span>
      {<span class="hljs-string">%</span> <span class="hljs-string">endfor</span> <span class="hljs-string">%</span>}
      <span class="hljs-attr">transformation_rules.py:</span> <span class="hljs-string">|
        # Custom transformation rules
        def transform_stripe_charge(charge):
            """Transform a Stripe charge record"""
            return {
                'sale_id': f"stripe_{charge['id']}",
                'transaction_date': charge['created'],
                'customer_id': charge.get('customer'),
                'product_id': 'stripe_payment',
                'quantity': 1,
                'unit_price': charge['amount'] / 100,  # Convert from cents
                'total_amount': charge['amount'] / 100,
                'payment_method': charge.get('payment_method_details', {}).get('type', 'unknown'),
                'region': charge.get('billing_details', {}).get('address', {}).get('country'),
                'sales_rep': 'stripe',
                'source_system': 'stripe',
                'metadata': {
                    'currency': charge.get('currency'),
                    'description': charge.get('description'),
                    'status': charge.get('status')
                }
            }
</span>
        <span class="hljs-string">def</span> <span class="hljs-string">transform_stripe_customer(customer):</span>
            <span class="hljs-string">""</span><span class="hljs-string">"Transform a Stripe customer record"</span><span class="hljs-string">""</span>
            <span class="hljs-string">return</span> {
                <span class="hljs-attr">'customer_id':</span> <span class="hljs-string">customer</span>[<span class="hljs-string">'id'</span>],
                <span class="hljs-attr">'customer_name':</span> <span class="hljs-string">customer.get('name')</span>,
                <span class="hljs-attr">'email':</span> <span class="hljs-string">customer.get('email')</span>,
                <span class="hljs-attr">'join_date':</span> <span class="hljs-string">customer.get('created')</span>,
                <span class="hljs-attr">'customer_tier':</span> <span class="hljs-string">'standard'</span>,
                <span class="hljs-attr">'lifetime_value':</span> <span class="hljs-string">customer.get('metadata'</span>, {}<span class="hljs-string">).get('lifetime_value'</span>, <span class="hljs-number">0</span><span class="hljs-string">)</span>,
                <span class="hljs-attr">'metadata':</span> {
                    <span class="hljs-attr">'description':</span> <span class="hljs-string">customer.get('description')</span>,
                    <span class="hljs-attr">'phone':</span> <span class="hljs-string">customer.get('phone')</span>,
                    <span class="hljs-attr">'address':</span> <span class="hljs-string">customer.get('address')</span>
                }
            }
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import numpy as np
      from datetime import datetime
      import json
      from pathlib import Path
</span>
      <span class="hljs-comment"># Import transformation rules</span>
      <span class="hljs-string">import</span> <span class="hljs-string">sys</span>
      <span class="hljs-string">sys.path.append('.')</span>
      <span class="hljs-string">from</span> <span class="hljs-string">transformation_rules</span> <span class="hljs-string">import</span> <span class="hljs-string">transform_stripe_charge,</span> <span class="hljs-string">transform_stripe_customer</span>

      <span class="hljs-comment"># Load data</span>
      <span class="hljs-string">charges_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_parquet('charges.parquet')</span>
      <span class="hljs-string">customers_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_parquet('customers.parquet')</span>
      <span class="hljs-string">refunds_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_parquet('refunds.parquet')</span>

      <span class="hljs-comment"># Transform charges</span>
      <span class="hljs-string">print(f"Transforming</span> {<span class="hljs-string">len(charges_df)</span>} <span class="hljs-string">charges...")</span>
      <span class="hljs-string">transformed_charges</span> <span class="hljs-string">=</span> []
      <span class="hljs-string">for</span> <span class="hljs-string">_,</span> <span class="hljs-string">charge</span> <span class="hljs-string">in</span> <span class="hljs-string">charges_df.iterrows():</span>
          <span class="hljs-attr">try:</span>
              <span class="hljs-string">transformed</span> <span class="hljs-string">=</span> <span class="hljs-string">transform_stripe_charge(charge.to_dict())</span>
              <span class="hljs-string">transformed_charges.append(transformed)</span>
          <span class="hljs-attr">except Exception as e:</span>
              <span class="hljs-string">print(f"Error</span> <span class="hljs-string">transforming</span> <span class="hljs-string">charge</span> {<span class="hljs-string">charge.get('id')</span>}<span class="hljs-string">:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")

      # Transform customers
      print(f"</span><span class="hljs-string">Transforming</span> {<span class="hljs-string">len(customers_df)</span>} <span class="hljs-string">customers...")</span>
      <span class="hljs-string">transformed_customers</span> <span class="hljs-string">=</span> []
      <span class="hljs-string">for</span> <span class="hljs-string">_,</span> <span class="hljs-string">customer</span> <span class="hljs-string">in</span> <span class="hljs-string">customers_df.iterrows():</span>
          <span class="hljs-attr">try:</span>
              <span class="hljs-string">transformed</span> <span class="hljs-string">=</span> <span class="hljs-string">transform_stripe_customer(customer.to_dict())</span>
              <span class="hljs-string">transformed_customers.append(transformed)</span>
          <span class="hljs-attr">except Exception as e:</span>
              <span class="hljs-string">print(f"Error</span> <span class="hljs-string">transforming</span> <span class="hljs-string">customer</span> {<span class="hljs-string">customer.get('id')</span>}<span class="hljs-string">:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")

      # Handle refunds (adjust charges)
      if len(refunds_df) &gt; 0:
          print(f"</span><span class="hljs-string">Processing</span> {<span class="hljs-string">len(refunds_df)</span>} <span class="hljs-string">refunds...")</span>
          <span class="hljs-string">refunds_by_charge</span> <span class="hljs-string">=</span> <span class="hljs-string">refunds_df.groupby('charge')</span>
          <span class="hljs-comment"># This would adjust the charges data</span>
          <span class="hljs-comment"># Implementation depends on business rules</span>

      <span class="hljs-comment"># Convert to DataFrames</span>
      <span class="hljs-string">sales_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(transformed_charges)</span>
      <span class="hljs-string">customers_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(transformed_customers)</span>

      <span class="hljs-comment"># Data validation</span>
      <span class="hljs-string">validation_errors</span> <span class="hljs-string">=</span> []

      <span class="hljs-comment"># Check for nulls in required fields</span>
      <span class="hljs-string">required_sales_fields</span> <span class="hljs-string">=</span> [<span class="hljs-string">'sale_id'</span>, <span class="hljs-string">'transaction_date'</span>, <span class="hljs-string">'total_amount'</span>]
      <span class="hljs-attr">for field in required_sales_fields:</span>
          <span class="hljs-string">null_count</span> <span class="hljs-string">=</span> <span class="hljs-string">sales_df[field].isnull().sum()</span>
          <span class="hljs-string">if</span> <span class="hljs-string">null_count</span> <span class="hljs-string">&gt;</span> <span class="hljs-attr">0:</span>
              <span class="hljs-string">validation_errors.append(f"Sales</span> {<span class="hljs-string">field</span>}<span class="hljs-string">:</span> {<span class="hljs-string">null_count</span>} <span class="hljs-literal">null</span> <span class="hljs-string">values")</span>

      <span class="hljs-comment"># Check for negative amounts</span>
      <span class="hljs-string">negative_amounts</span> <span class="hljs-string">=</span> <span class="hljs-string">(sales_df['total_amount']</span> <span class="hljs-string">&lt;</span> <span class="hljs-number">0</span><span class="hljs-string">).sum()</span>
      <span class="hljs-string">if</span> <span class="hljs-string">negative_amounts</span> <span class="hljs-string">&gt;</span> <span class="hljs-attr">0:</span>
          <span class="hljs-string">validation_errors.append(f"Sales:</span> {<span class="hljs-string">negative_amounts</span>} <span class="hljs-string">negative</span> <span class="hljs-string">amounts")</span>

      <span class="hljs-comment"># Check for future dates</span>
      <span class="hljs-string">today</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.Timestamp.now()</span>
      <span class="hljs-string">future_dates</span> <span class="hljs-string">=</span> <span class="hljs-string">(pd.to_datetime(sales_df['transaction_date'])</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">today).sum()</span>
      <span class="hljs-string">if</span> <span class="hljs-string">future_dates</span> <span class="hljs-string">&gt;</span> <span class="hljs-attr">0:</span>
          <span class="hljs-string">validation_errors.append(f"Sales:</span> {<span class="hljs-string">future_dates</span>} <span class="hljs-string">future</span> <span class="hljs-string">dates")</span>

      <span class="hljs-comment"># Save validation report</span>
      <span class="hljs-string">validation_report</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'transformation_date':</span> <span class="hljs-string">datetime.now().isoformat()</span>,
          <span class="hljs-attr">'records_processed':</span> {
              <span class="hljs-attr">'sales':</span> <span class="hljs-string">len(sales_df)</span>,
              <span class="hljs-attr">'customers':</span> <span class="hljs-string">len(customers_df)</span>
          },
          <span class="hljs-attr">'validation_errors':</span> <span class="hljs-string">validation_errors</span>,
          <span class="hljs-attr">'error_rate':</span> <span class="hljs-string">len(validation_errors)</span> <span class="hljs-string">/</span> <span class="hljs-string">(len(sales_df)</span> <span class="hljs-string">+</span> <span class="hljs-string">len(customers_df))</span> <span class="hljs-string">if</span> <span class="hljs-string">(len(sales_df)</span> <span class="hljs-string">+</span> <span class="hljs-string">len(customers_df))</span> <span class="hljs-string">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>
      }

      <span class="hljs-string">with</span> <span class="hljs-string">open('validation_report.json',</span> <span class="hljs-string">'w'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">json.dump(validation_report,</span> <span class="hljs-string">f,</span> <span class="hljs-string">indent=2)</span>

      <span class="hljs-comment"># Save transformed data</span>
      <span class="hljs-string">sales_df.to_parquet('stripe_sales_transformed.parquet',</span> <span class="hljs-string">index=False)</span>
      <span class="hljs-string">customers_df.to_parquet('stripe_customers_transformed.parquet',</span> <span class="hljs-string">index=False)</span>

      <span class="hljs-comment"># Output to Kestra</span>
      <span class="hljs-string">output</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'transformed_files':</span> {
              <span class="hljs-attr">'sales':</span> <span class="hljs-string">'stripe_sales_transformed.parquet'</span>,
              <span class="hljs-attr">'customers':</span> <span class="hljs-string">'stripe_customers_transformed.parquet'</span>
          },
          <span class="hljs-attr">'validation':</span> <span class="hljs-string">validation_report</span>,
          <span class="hljs-attr">'summary':</span> {
              <span class="hljs-attr">'total_sales':</span> <span class="hljs-string">len(sales_df)</span>,
              <span class="hljs-attr">'total_customers':</span> <span class="hljs-string">len(customers_df)</span>,
              <span class="hljs-attr">'total_revenue':</span> <span class="hljs-string">sales_df</span>[<span class="hljs-string">'total_amount'</span>]<span class="hljs-string">.sum()</span> <span class="hljs-string">if</span> <span class="hljs-string">len(sales_df)</span> <span class="hljs-string">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>
          }
      }

      <span class="hljs-string">print(json.dumps(output))</span>

    <span class="hljs-attr">taskRunner:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">4Gi</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-number">2</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">python:3.11-slim</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">handle_validation_errors</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.load_and_transform.vars.validation.error_rate }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">0:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">log_success</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
          <span class="hljs-attr">message:</span> <span class="hljs-string">"Stripe transformation successful with 0 validation errors"</span>

      <span class="hljs-attr">0.01:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">log_warning</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
          <span class="hljs-attr">message:</span> <span class="hljs-string">"Stripe transformation completed with <span class="hljs-template-variable">{{ (outputs.load_and_transform.vars.validation.error_rate * 100) | round(2) }}</span>% error rate"</span>

      <span class="hljs-attr">default:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">alert_high_error_rate</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
          <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_WEBHOOK_URL') }}</span>"</span>
          <span class="hljs-attr">payload:</span> <span class="hljs-string">|
            {
              "blocks": [
                {
                  "type": "header",
                  "text": {
                    "type": "plain_text",
                    "text": "⚠️ High Error Rate in Stripe Transformation"
                  }
                },
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "Error rate: *{{ (outputs.load_and_transform.vars.validation.error_rate * 100) | round(2) }}%*\n\nValidation errors:\n{% for error in outputs.load_and_transform.vars.validation.validation_errors %}- {{ error }}\n{% endfor %}"
                  }
                }
              ]
            }</span>
</code></pre>
<h3 id="heading-data-quality-transformation"><strong>Data Quality Transformation</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/transform/data-quality-checks.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">run-data-quality-checks</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.quality</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Run</span> <span class="hljs-string">comprehensive</span> <span class="hljs-string">data</span> <span class="hljs-string">quality</span> <span class="hljs-string">checks</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">transformed_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Path to transformed data file"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">validation_rules</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">JSON</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Data quality validation rules"</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_great_expectations</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.greatexpectations.Validation</span>
    <span class="hljs-attr">expectationSuite:</span> <span class="hljs-string">|
      {
        "expectation_suite_name": "sales_data_suite",
        "expectations": [
          {
            "expectation_type": "expect_column_values_to_not_be_null",
            "kwargs": {
              "column": "sale_id"
            }
          },
          {
            "expectation_type": "expect_column_values_to_be_between",
            "kwargs": {
              "column": "total_amount",
              "min_value": 0,
              "max_value": 1000000
            }
          },
          {
            "expectation_type": "expect_column_values_to_be_in_set",
            "kwargs": {
              "column": "source_system",
              "value_set": ["stripe", "shopify", "internal", "manual"]
            }
          }
        ]
      }
</span>    <span class="hljs-attr">data:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">file</span>
      <span class="hljs-attr">path:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.transformed_data }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_data_profiling</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">data.parquet:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.transformed_data }}</span>"</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import json
      from ydata_profiling import ProfileReport
</span>
      <span class="hljs-comment"># Load data</span>
      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_parquet('data.parquet')</span>

      <span class="hljs-comment"># Generate profile</span>
      <span class="hljs-string">profile</span> <span class="hljs-string">=</span> <span class="hljs-string">ProfileReport(df,</span> <span class="hljs-string">title="Sales</span> <span class="hljs-string">Data</span> <span class="hljs-string">Profile")</span>
      <span class="hljs-string">profile.to_file("data_profile.html")</span>

      <span class="hljs-comment"># Calculate statistics</span>
      <span class="hljs-string">stats</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'row_count':</span> <span class="hljs-string">len(df)</span>,
          <span class="hljs-attr">'column_count':</span> <span class="hljs-string">len(df.columns)</span>,
          <span class="hljs-attr">'missing_values':</span> <span class="hljs-string">df.isnull().sum().to_dict()</span>,
          <span class="hljs-attr">'data_types':</span> {<span class="hljs-attr">col:</span> <span class="hljs-string">str(dtype)</span> <span class="hljs-string">for</span> <span class="hljs-string">col</span>, <span class="hljs-string">dtype</span> <span class="hljs-string">in</span> <span class="hljs-string">df.dtypes.items()</span>},
          <span class="hljs-attr">'numeric_stats':</span> {},
          <span class="hljs-attr">'categorical_stats':</span> {}
      }

      <span class="hljs-comment"># Numeric columns</span>
      <span class="hljs-string">numeric_cols</span> <span class="hljs-string">=</span> <span class="hljs-string">df.select_dtypes(include=['number']).columns</span>
      <span class="hljs-attr">for col in numeric_cols:</span>
          <span class="hljs-string">stats['numeric_stats'][col]</span> <span class="hljs-string">=</span> {
              <span class="hljs-attr">'mean':</span> <span class="hljs-string">float(df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.mean())</span>,
              <span class="hljs-attr">'std':</span> <span class="hljs-string">float(df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.std())</span>,
              <span class="hljs-attr">'min':</span> <span class="hljs-string">float(df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.min())</span>,
              <span class="hljs-attr">'max':</span> <span class="hljs-string">float(df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.max())</span>,
              <span class="hljs-attr">'median':</span> <span class="hljs-string">float(df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.median())</span>
          }

      <span class="hljs-comment"># Categorical columns</span>
      <span class="hljs-string">categorical_cols</span> <span class="hljs-string">=</span> <span class="hljs-string">df.select_dtypes(include=['object']).columns</span>
      <span class="hljs-attr">for col in categorical_cols:</span>
          <span class="hljs-string">stats['categorical_stats'][col]</span> <span class="hljs-string">=</span> {
              <span class="hljs-attr">'unique_count':</span> <span class="hljs-string">int(df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.nunique())</span>,
              <span class="hljs-attr">'top_value':</span> <span class="hljs-string">df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.mode().iloc</span>[<span class="hljs-number">0</span>] <span class="hljs-string">if</span> <span class="hljs-string">not</span> <span class="hljs-string">df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.mode().empty</span> <span class="hljs-string">else</span> <span class="hljs-string">None</span>,
              <span class="hljs-attr">'top_frequency':</span> <span class="hljs-string">int(df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.value_counts().iloc</span>[<span class="hljs-number">0</span>]<span class="hljs-string">)</span> <span class="hljs-string">if</span> <span class="hljs-string">not</span> <span class="hljs-string">df</span>[<span class="hljs-string">col</span>]<span class="hljs-string">.value_counts().empty</span> <span class="hljs-string">else</span> <span class="hljs-number">0</span>
          }

      <span class="hljs-comment"># Save stats</span>
      <span class="hljs-string">with</span> <span class="hljs-string">open('data_statistics.json',</span> <span class="hljs-string">'w'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">json.dump(stats,</span> <span class="hljs-string">f,</span> <span class="hljs-string">indent=2)</span>

      <span class="hljs-string">print(json.dumps({'profile_file':</span> <span class="hljs-string">'data_profile.html'</span><span class="hljs-string">,</span> <span class="hljs-attr">'statistics':</span> <span class="hljs-string">stats}))</span>
</code></pre>
<hr />
<h2 id="heading-phase-5-building-the-load-layer"><strong>Phase 5: Building the Load Layer</strong></h2>
<h3 id="heading-data-warehouse-loading-strategy"><strong>Data Warehouse Loading Strategy</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/load/load-to-warehouse.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">load-to-warehouse</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Load</span> <span class="hljs-string">transformed</span> <span class="hljs-string">data</span> <span class="hljs-string">to</span> <span class="hljs-string">data</span> <span class="hljs-string">warehouse</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">warehouse_type:</span> <span class="hljs-string">snowflake</span>  <span class="hljs-comment"># or bigquery, redshift, postgresql</span>
  <span class="hljs-attr">load_strategy:</span> <span class="hljs-string">incremental</span>  <span class="hljs-comment"># or full, upsert</span>
  <span class="hljs-attr">batch_id:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.id }}</span>"</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">sales_data_path</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Path to transformed sales data"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">customer_data_path</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Path to transformed customer data"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">load_date</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() }}</span>"</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Task 1: Create staging tables</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">create_staging_tables</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      CREATE OR REPLACE TEMPORARY TABLE staging_sales (
        sale_id VARCHAR(100),
        transaction_date DATE,
        customer_id VARCHAR(50),
        product_id VARCHAR(50),
        quantity INTEGER,
        unit_price DECIMAL(10,2),
        total_amount DECIMAL(10,2),
        payment_method VARCHAR(50),
        region VARCHAR(100),
        sales_rep VARCHAR(100),
        source_system VARCHAR(50),
        loaded_at TIMESTAMP,
        batch_id VARCHAR(100)
      );
</span>
      <span class="hljs-string">CREATE</span> <span class="hljs-string">OR</span> <span class="hljs-string">REPLACE</span> <span class="hljs-string">TEMPORARY</span> <span class="hljs-string">TABLE</span> <span class="hljs-string">staging_customers</span> <span class="hljs-string">(</span>
        <span class="hljs-string">customer_id</span> <span class="hljs-string">VARCHAR(50),</span>
        <span class="hljs-string">customer_name</span> <span class="hljs-string">VARCHAR(255),</span>
        <span class="hljs-string">email</span> <span class="hljs-string">VARCHAR(255),</span>
        <span class="hljs-string">join_date</span> <span class="hljs-string">DATE,</span>
        <span class="hljs-string">customer_tier</span> <span class="hljs-string">VARCHAR(50),</span>
        <span class="hljs-string">lifetime_value</span> <span class="hljs-string">DECIMAL(10,2),</span>
        <span class="hljs-string">last_updated</span> <span class="hljs-string">TIMESTAMP,</span>
        <span class="hljs-string">batch_id</span> <span class="hljs-string">VARCHAR(100)</span>
      <span class="hljs-string">);</span>
    <span class="hljs-attr">role:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_ROLE') }}</span>"</span>
    <span class="hljs-attr">warehouse:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_WAREHOUSE') }}</span>"</span>

  <span class="hljs-comment"># Task 2: Load data to staging (parallel)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_staging_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_sales_staging</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Load</span>
        <span class="hljs-attr">from:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.sales_data_path }}</span>"</span>
        <span class="hljs-attr">table:</span> <span class="hljs-string">staging_sales</span>
        <span class="hljs-attr">schema:</span> <span class="hljs-string">TEMPORARY</span>
        <span class="hljs-attr">fileFormat:</span> <span class="hljs-string">"(TYPE = PARQUET)"</span>
        <span class="hljs-attr">copyOptions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">ON_ERROR</span> <span class="hljs-string">=</span> <span class="hljs-string">CONTINUE</span>
        <span class="hljs-attr">role:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_ROLE') }}</span>"</span>
        <span class="hljs-attr">warehouse:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_WAREHOUSE') }}</span>"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_customers_staging</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Load</span>
        <span class="hljs-attr">from:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.customer_data_path }}</span>"</span>
        <span class="hljs-attr">table:</span> <span class="hljs-string">staging_customers</span>
        <span class="hljs-attr">schema:</span> <span class="hljs-string">TEMPORARY</span>
        <span class="hljs-attr">fileFormat:</span> <span class="hljs-string">"(TYPE = PARQUET)"</span>
        <span class="hljs-attr">copyOptions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">ON_ERROR</span> <span class="hljs-string">=</span> <span class="hljs-string">CONTINUE</span>
        <span class="hljs-attr">role:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_ROLE') }}</span>"</span>
        <span class="hljs-attr">warehouse:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_WAREHOUSE') }}</span>"</span>

  <span class="hljs-comment"># Task 3: Validate staging data</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_staging_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      WITH staging_stats AS (
        SELECT 
          COUNT(*) as staging_count,
          COUNT(DISTINCT sale_id) as unique_sales,
          SUM(CASE WHEN sale_id IS NULL THEN 1 ELSE 0 END) as null_sales_ids,
          SUM(CASE WHEN total_amount &lt; 0 THEN 1 ELSE 0 END) as negative_amounts
        FROM staging_sales
      ),
      existing_stats AS (
        SELECT COUNT(*) as existing_count
        FROM sales_fact 
        WHERE DATE(loaded_at) = '{{ inputs.load_date | date('yyyy-MM-dd') }}'
      )
      SELECT 
        s.staging_count,
        s.unique_sales,
        s.null_sales_ids,
        s.negative_amounts,
        e.existing_count,
        CASE 
          WHEN s.null_sales_ids &gt; 0 THEN 'FAIL'
          WHEN s.negative_amounts &gt; s.staging_count * 0.01 THEN 'WARN'
          ELSE 'PASS'
        END as validation_status
      FROM staging_stats s
      CROSS JOIN existing_stats e
</span>    <span class="hljs-attr">role:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_ROLE') }}</span>"</span>
    <span class="hljs-attr">warehouse:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_WAREHOUSE') }}</span>"</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">load_staging_data</span>

  <span class="hljs-comment"># Task 4: Merge to production (incremental load)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">merge_to_production</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      -- Merge sales data
      MERGE INTO sales_fact AS target
      USING staging_sales AS source
      ON target.sale_id = source.sale_id
      AND target.transaction_date = source.transaction_date
      WHEN MATCHED THEN
        UPDATE SET 
          customer_id = source.customer_id,
          product_id = source.product_id,
          quantity = source.quantity,
          unit_price = source.unit_price,
          total_amount = source.total_amount,
          payment_method = source.payment_method,
          region = source.region,
          sales_rep = source.sales_rep,
          source_system = source.source_system,
          loaded_at = source.loaded_at,
          batch_id = source.batch_id
      WHEN NOT MATCHED THEN
        INSERT (
          sale_id, transaction_date, customer_id, product_id,
          quantity, unit_price, total_amount, payment_method,
          region, sales_rep, source_system, loaded_at, batch_id
        )
        VALUES (
          source.sale_id, source.transaction_date, source.customer_id, source.product_id,
          source.quantity, source.unit_price, source.total_amount, source.payment_method,
          source.region, source.sales_rep, source.source_system, source.loaded_at, source.batch_id
        );
</span>
      <span class="hljs-string">--</span> <span class="hljs-string">Merge</span> <span class="hljs-string">customer</span> <span class="hljs-string">data</span>
      <span class="hljs-string">MERGE</span> <span class="hljs-string">INTO</span> <span class="hljs-string">customer_dim</span> <span class="hljs-string">AS</span> <span class="hljs-string">target</span>
      <span class="hljs-string">USING</span> <span class="hljs-string">staging_customers</span> <span class="hljs-string">AS</span> <span class="hljs-string">source</span>
      <span class="hljs-string">ON</span> <span class="hljs-string">target.customer_id</span> <span class="hljs-string">=</span> <span class="hljs-string">source.customer_id</span>
      <span class="hljs-string">WHEN</span> <span class="hljs-string">MATCHED</span> <span class="hljs-string">AND</span> <span class="hljs-string">target.last_updated</span> <span class="hljs-string">&lt;</span> <span class="hljs-string">source.last_updated</span> <span class="hljs-string">THEN</span>
        <span class="hljs-string">UPDATE</span> <span class="hljs-string">SET</span> 
          <span class="hljs-string">customer_name</span> <span class="hljs-string">=</span> <span class="hljs-string">source.customer_name,</span>
          <span class="hljs-string">email</span> <span class="hljs-string">=</span> <span class="hljs-string">source.email,</span>
          <span class="hljs-string">join_date</span> <span class="hljs-string">=</span> <span class="hljs-string">source.join_date,</span>
          <span class="hljs-string">customer_tier</span> <span class="hljs-string">=</span> <span class="hljs-string">source.customer_tier,</span>
          <span class="hljs-string">lifetime_value</span> <span class="hljs-string">=</span> <span class="hljs-string">source.lifetime_value,</span>
          <span class="hljs-string">last_updated</span> <span class="hljs-string">=</span> <span class="hljs-string">source.last_updated</span>
      <span class="hljs-string">WHEN</span> <span class="hljs-string">NOT</span> <span class="hljs-string">MATCHED</span> <span class="hljs-string">THEN</span>
        <span class="hljs-string">INSERT</span> <span class="hljs-string">(</span>
          <span class="hljs-string">customer_id,</span> <span class="hljs-string">customer_name,</span> <span class="hljs-string">email,</span> <span class="hljs-string">join_date,</span>
          <span class="hljs-string">customer_tier,</span> <span class="hljs-string">lifetime_value,</span> <span class="hljs-string">last_updated</span>
        <span class="hljs-string">)</span>
        <span class="hljs-string">VALUES</span> <span class="hljs-string">(</span>
          <span class="hljs-string">source.customer_id,</span> <span class="hljs-string">source.customer_name,</span> <span class="hljs-string">source.email,</span> <span class="hljs-string">source.join_date,</span>
          <span class="hljs-string">source.customer_tier,</span> <span class="hljs-string">source.lifetime_value,</span> <span class="hljs-string">source.last_updated</span>
        <span class="hljs-string">);</span>
    <span class="hljs-attr">role:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_ROLE') }}</span>"</span>
    <span class="hljs-attr">warehouse:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_WAREHOUSE') }}</span>"</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">validate_staging_data</span>

  <span class="hljs-comment"># Task 5: Generate load statistics</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_load_stats</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      SELECT 
        'sales_fact' as table_name,
        COUNT(*) as total_rows,
        COUNT(DISTINCT sale_id) as unique_sales,
        SUM(total_amount) as total_revenue,
        MIN(transaction_date) as earliest_date,
        MAX(transaction_date) as latest_date
      FROM sales_fact
      WHERE batch_id = '{{ vars.batch_id }}'
</span>
      <span class="hljs-string">UNION</span> <span class="hljs-string">ALL</span>

      <span class="hljs-string">SELECT</span> 
        <span class="hljs-string">'customer_dim'</span> <span class="hljs-string">as</span> <span class="hljs-string">table_name,</span>
        <span class="hljs-string">COUNT(*)</span> <span class="hljs-string">as</span> <span class="hljs-string">total_customers,</span>
        <span class="hljs-string">COUNT(DISTINCT</span> <span class="hljs-string">customer_id)</span> <span class="hljs-string">as</span> <span class="hljs-string">unique_customers,</span>
        <span class="hljs-string">SUM(lifetime_value)</span> <span class="hljs-string">as</span> <span class="hljs-string">total_lifetime_value,</span>
        <span class="hljs-string">MIN(join_date)</span> <span class="hljs-string">as</span> <span class="hljs-string">earliest_join,</span>
        <span class="hljs-string">MAX(join_date)</span> <span class="hljs-string">as</span> <span class="hljs-string">latest_join</span>
      <span class="hljs-string">FROM</span> <span class="hljs-string">customer_dim</span>
      <span class="hljs-string">WHERE</span> <span class="hljs-string">last_updated</span> <span class="hljs-string">&gt;=</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.load_date | date('yyyy-MM-dd') }}</span>'</span>
    <span class="hljs-attr">role:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_ROLE') }}</span>"</span>
    <span class="hljs-attr">warehouse:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SNOWFLAKE_WAREHOUSE') }}</span>"</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">merge_to_production</span>

  <span class="hljs-comment"># Task 6: Send load completion notification</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_load_notification</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_WEBHOOK_URL') }}</span>"</span>
    <span class="hljs-attr">payload:</span> <span class="hljs-string">|
      {
        "blocks": [
          {
            "type": "header",
            "text": {
              "type": "plain_text",
              "text": "✅ ETL Load Complete"
            }
          },
          {
            "type": "section",
            "fields": [
              {
                "type": "mrkdwn",
                "text": "*Load Date:*\n{{ inputs.load_date | date('yyyy-MM-dd HH:mm') }}"
              },
              {
                "type": "mrkdwn",
                "text": "*Batch ID:*\n{{ vars.batch_id }}"
              }
            ]
          },
          {% for row in outputs.generate_load_stats.rows %}
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*{{ row.table_name }}*\nRows: {{ row.total_rows }}\n{% if row.table_name == 'sales_fact' %}Revenue: ${{ row.total_revenue | round(2) }}{% else %}LTV: ${{ row.total_lifetime_value | round(2) }}{% endif %}"
            }
          },
          {% endfor %}
          {
            "type": "context",
            "elements": [
              {
                "type": "mrkdwn",
                "text": "Execution: {{ execution.id }}"
              }
            ]
          }
        ]
      }
</span>
    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">generate_load_stats</span>
</code></pre>
<h3 id="heading-alternative-bigquery-loading"><strong>Alternative: BigQuery Loading</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/load/load-to-bigquery.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">load-to-bigquery</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_sales_to_bigquery</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.gcp.bigquery.Load</span>
    <span class="hljs-attr">from:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.sales_data_path }}</span>"</span>
    <span class="hljs-attr">destinationTable:</span>
      <span class="hljs-attr">projectId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('GCP_PROJECT_ID') }}</span>"</span>
      <span class="hljs-attr">datasetId:</span> <span class="hljs-string">sales_data</span>
      <span class="hljs-attr">tableId:</span> <span class="hljs-string">sales_fact</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">PARQUET</span>
    <span class="hljs-attr">writeDisposition:</span> <span class="hljs-string">WRITE_APPEND</span>
    <span class="hljs-attr">createDisposition:</span> <span class="hljs-string">CREATE_IF_NEEDED</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_dbt_transformation</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.dbt.cli.Run</span>
    <span class="hljs-attr">projectDir:</span> <span class="hljs-string">"s3://acme-dbt-project/"</span>
    <span class="hljs-attr">profilesDir:</span> <span class="hljs-string">"s3://acme-dbt-profiles/"</span>
    <span class="hljs-attr">target:</span> <span class="hljs-string">prod</span>
    <span class="hljs-attr">select:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">sales_fact+</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">customer_dim+</span>
</code></pre>
<hr />
<h2 id="heading-phase-6-orchestrating-the-complete-pipeline"><strong>Phase 6: Orchestrating the Complete Pipeline</strong></h2>
<h3 id="heading-master-orchestrator-flow"><strong>Master Orchestrator Flow</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/orchestrator/daily-sales-etl.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">daily-sales-etl</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales</span>
<span class="hljs-attr">revision:</span> <span class="hljs-number">1</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">|
  # Daily Sales ETL Pipeline
</span>
  <span class="hljs-string">Complete</span> <span class="hljs-string">end-to-end</span> <span class="hljs-string">pipeline</span> <span class="hljs-string">for</span> <span class="hljs-string">sales</span> <span class="hljs-string">data.</span>
  <span class="hljs-string">Extracts</span> <span class="hljs-string">from</span> <span class="hljs-number">4</span> <span class="hljs-string">sources,</span> <span class="hljs-string">transforms,</span> <span class="hljs-string">validates,</span> <span class="hljs-string">and</span> <span class="hljs-string">loads</span> <span class="hljs-string">to</span> <span class="hljs-string">warehouse.</span>

  <span class="hljs-comment">## SLA: 4 hours</span>
  <span class="hljs-comment">## Schedule: Daily at 1 AM UTC</span>
  <span class="hljs-comment">## Owner: Data Engineering Team</span>

<span class="hljs-attr">labels:</span>
  <span class="hljs-attr">pipeline:</span> <span class="hljs-string">sales-etl</span>
  <span class="hljs-attr">tier:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">sla:</span> <span class="hljs-number">4</span><span class="hljs-string">-hours</span>
  <span class="hljs-attr">owner:</span> <span class="hljs-string">data-engineering</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">pipeline_version:</span> <span class="hljs-string">"1.0"</span>
  <span class="hljs-attr">notification_channels:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">slack:</span> <span class="hljs-string">"#data-alerts"</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">email:</span> <span class="hljs-string">"data-eng@acme.com"</span>
  <span class="hljs-attr">retry_policy:</span>
    <span class="hljs-attr">max_attempts:</span> <span class="hljs-number">3</span>
    <span class="hljs-attr">delay_minutes:</span> <span class="hljs-number">5</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">execution_date</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() | date('yyyy-MM-dd') }}</span>"</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">reprocess_full_day</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">BOOLEAN</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># ========== PHASE 1: SETUP ==========</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">initialize_pipeline</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Pipeline initialization and validation"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_execution_date</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
          {{ inputs.execution_date | date('yyyy-MM-dd') &lt;= 
             now() | date('yyyy-MM-dd') }}
</span>        <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Execution date cannot be in the future"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_previous_executions</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Previous</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ namespace.id }}</span>"</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ flow.id }}</span>"</span>
        <span class="hljs-attr">successOnly:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">should_execute</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
          {{ inputs.reprocess_full_day }} or
          {{ outputs.check_previous_executions.executions | length == 0 }} or
          {{ outputs.check_previous_executions.executions[0].state.current != 'SUCCESS' }}
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">log_pipeline_start</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
        <span class="hljs-attr">message:</span> <span class="hljs-string">|
          🚀 Starting Daily Sales ETL Pipeline
          Date: {{ inputs.execution_date }}
          Pipeline Version: {{ vars.pipeline_version }}
          Execution ID: {{ execution.id }}
</span>
  <span class="hljs-comment"># ========== PHASE 2: EXTRACTION ==========</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_all_sources</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Extract data from all sources in parallel"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_stripe</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">extract-stripe-payments</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">start_date</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.execution_date }}</span>"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">end_date</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.execution_date }}</span>"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_shopify</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">extract-shopify-orders</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_internal</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">extract-internal-sales</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_manual</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">extract-google-sheets</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.initialize_pipeline.outputs.should_execute.value }}</span>"</span>

  <span class="hljs-comment"># ========== PHASE 3: TRANSFORMATION ==========</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_and_validate</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Transform and validate extracted data"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_transformations</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">master-transformer</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_transformation_quality</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
          {{ outputs.run_transformations.outputs.data_quality_check.outputs.quality_score | default(100) }} &gt;= 95
</span>        <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Data quality score below 95%"</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">extract_all_sources</span>

  <span class="hljs-comment"># ========== PHASE 4: LOADING ==========</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_to_data_warehouse</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">load-to-warehouse</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">sales_data_path</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_and_validate.outputs.run_transformations.outputs.calculate_metrics.outputs.final_data }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">customer_data_path</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_and_validate.outputs.run_transformations.outputs.enrich_customer_data.outputs.customer_file }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">load_date</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.execution_date }}</span>"</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">transform_and_validate</span>

  <span class="hljs-comment"># ========== PHASE 5: POST-PROCESSING ==========</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">post_processing</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Post-load processing and reporting"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_daily_report</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.reporting</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">generate-daily-sales-report</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">report_date</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.execution_date }}</span>"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_data_catalog</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.governance</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">update-data-catalog</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">archive_raw_data</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.storage</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">archive-raw-data</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">load_to_data_warehouse</span>

  <span class="hljs-comment"># ========== PHASE 6: COMPLETION ==========</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">pipeline_completion</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Pipeline completion and notifications"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">calculate_pipeline_metrics</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">&gt;
          {
            "start_time": "{{ execution.startDate }}",
            "end_time": "{{ now() }}",
            "duration_seconds": {{ (now() - execution.startDate).totalSeconds() | round(2) }},
            "extraction_records": {{ outputs.extract_all_sources.outputs.extract_stripe.outputs.merge_stripe_data.vars.validation.total_records | default(0) }},
            "loaded_records": {{ outputs.load_to_data_warehouse.outputs.generate_load_stats.rows[0].total_rows | default(0) }},
            "success_rate": 100
          }
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_success_notification</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_WEBHOOK_URL') }}</span>"</span>
        <span class="hljs-attr">payload:</span> <span class="hljs-string">|
          {
            "blocks": [
              {
                "type": "header",
                "text": {
                  "type": "plain_text",
                  "text": "🎉 Daily Sales ETL Complete"
                }
              },
              {
                "type": "section",
                "fields": [
                  {
                    "type": "mrkdwn",
                    "text": "*Date:*\n{{ inputs.execution_date }}"
                  },
                  {
                    "type": "mrkdwn",
                    "text": "*Duration:*\n{{ outputs.calculate_pipeline_metrics.value.duration_seconds | round(2) }} seconds"
                  }
                ]
              },
              {
                "type": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "*Records Processed*\nExtracted: {{ outputs.calculate_pipeline_metrics.value.extraction_records }}\nLoaded: {{ outputs.calculate_pipeline_metrics.value.loaded_records }}"
                }
              }
            ]
          }
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_monitoring_dashboard</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('MONITORING_API_URL') }}</span>/api/v1/metrics"</span>
        <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">headers:</span>
          <span class="hljs-attr">Authorization:</span> <span class="hljs-string">"Bearer <span class="hljs-template-variable">{{ secret('MONITORING_API_KEY') }}</span>"</span>
        <span class="hljs-attr">body:</span> <span class="hljs-string">|
          {
            "metric": "etl_pipeline_duration",
            "value": {{ outputs.calculate_pipeline_metrics.value.duration_seconds }},
            "tags": {
              "pipeline": "daily-sales-etl",
              "date": "{{ inputs.execution_date }}"
            }
          }
</span>
    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">post_processing</span>

  <span class="hljs-comment"># ========== ERROR HANDLING ==========</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">handle_pipeline_failure</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Handle pipeline failures"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_failure_alert</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackIncomingWebhook</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('SLACK_CRITICAL_WEBHOOK_URL') }}</span>"</span>
        <span class="hljs-attr">payload:</span> <span class="hljs-string">|
          {
            "blocks": [
              {
                "type": "header",
                "text": {
                  "type": "plain_text",
                  "text": "🚨 ETL Pipeline Failure"
                }
              },
              {
                "type": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "*Pipeline:* Daily Sales ETL\n*Date:* {{ inputs.execution_date }}\n*Error:* {{ execution.state.current }}\n*Execution:* {{ execution.id }}"
                }
              },
              {
                "type": "actions",
                "elements": [
                  {
                    "type": "button",
                    "text": {
                      "type": "plain_text",
                      "text": "View Details"
                    },
                    "url": "{{ serverUrl }}/ui/executions/{{ execution.id }}"
                  }
                ]
              }
            ]
          }
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">trigger_backup_pipeline</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.backup</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">run-backup-sales-pipeline</span>
        <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>  <span class="hljs-comment"># Fire and forget</span>

    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Execution</span>
        <span class="hljs-attr">states:</span> [<span class="hljs-string">FAILED</span>, <span class="hljs-string">KILLED</span>, <span class="hljs-string">WARNING</span>]

<span class="hljs-attr">triggers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">daily_schedule</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Schedule</span>
    <span class="hljs-attr">cron:</span> <span class="hljs-string">"0 1 * * *"</span>  <span class="hljs-comment"># 1 AM UTC daily</span>
    <span class="hljs-attr">timezone:</span> <span class="hljs-string">UTC</span>
    <span class="hljs-attr">inputs:</span>
      <span class="hljs-attr">execution_date:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() | date('yyyy-MM-dd') }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">manual_trigger</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Webhook</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">daily-sales-etl</span>
    <span class="hljs-attr">inputs:</span>
      <span class="hljs-attr">execution_date:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ trigger.date | default(now() | date('yyyy-MM-dd')) }}</span>"</span>
      <span class="hljs-attr">reprocess_full_day:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ trigger.reprocess | default(false) }}</span>"</span>

<span class="hljs-attr">timeout:</span> <span class="hljs-string">PT4H</span>  <span class="hljs-comment"># 4 hour timeout</span>

<span class="hljs-attr">listeners:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Execution</span>
        <span class="hljs-attr">states:</span> [<span class="hljs-string">FAILED</span>]
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">emergency_cleanup</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">system.emergency</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">cleanup-failed-pipeline</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">execution_id</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-phase-7-testing-and-monitoring"><strong>Phase 7: Testing and Monitoring</strong></h2>
<h3 id="heading-unit-tests-for-transformation-logic"><strong>Unit Tests for Transformation Logic</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># tests/unit/test_transformations.py</span>
<span class="hljs-string">import</span> <span class="hljs-string">pytest</span>
<span class="hljs-string">import</span> <span class="hljs-string">pandas</span> <span class="hljs-string">as</span> <span class="hljs-string">pd</span>
<span class="hljs-string">from</span> <span class="hljs-string">datetime</span> <span class="hljs-string">import</span> <span class="hljs-string">datetime</span>
<span class="hljs-string">import</span> <span class="hljs-string">sys</span>
<span class="hljs-string">sys.path.append('../scripts')</span>
<span class="hljs-string">from</span> <span class="hljs-string">transformations</span> <span class="hljs-string">import</span> <span class="hljs-string">transform_stripe_charge,</span> <span class="hljs-string">validate_sales_data</span>

<span class="hljs-string">def</span> <span class="hljs-string">test_transform_stripe_charge():</span>
    <span class="hljs-string">""</span><span class="hljs-string">"Test Stripe charge transformation"</span><span class="hljs-string">""</span>
    <span class="hljs-string">sample_charge</span> <span class="hljs-string">=</span> {
        <span class="hljs-attr">'id':</span> <span class="hljs-string">'ch_123'</span>,
        <span class="hljs-attr">'created':</span> <span class="hljs-number">1672531200</span>,  <span class="hljs-comment"># 2023-01-01</span>
        <span class="hljs-attr">'customer':</span> <span class="hljs-string">'cus_456'</span>,
        <span class="hljs-attr">'amount':</span> <span class="hljs-number">1000</span>,
        <span class="hljs-attr">'currency':</span> <span class="hljs-string">'usd'</span>,
        <span class="hljs-attr">'payment_method_details':</span> {<span class="hljs-attr">'type':</span> <span class="hljs-string">'card'</span>},
        <span class="hljs-attr">'billing_details':</span> {<span class="hljs-attr">'address':</span> {<span class="hljs-attr">'country':</span> <span class="hljs-string">'US'</span>}}
    }

    <span class="hljs-string">result</span> <span class="hljs-string">=</span> <span class="hljs-string">transform_stripe_charge(sample_charge)</span>

    <span class="hljs-string">assert</span> <span class="hljs-string">result['sale_id']</span> <span class="hljs-string">==</span> <span class="hljs-string">'stripe_ch_123'</span>
    <span class="hljs-string">assert</span> <span class="hljs-string">result['total_amount']</span> <span class="hljs-string">==</span> <span class="hljs-number">10.0</span>  <span class="hljs-comment"># Converted from cents</span>
    <span class="hljs-string">assert</span> <span class="hljs-string">result['region']</span> <span class="hljs-string">==</span> <span class="hljs-string">'US'</span>
    <span class="hljs-string">assert</span> <span class="hljs-string">result['source_system']</span> <span class="hljs-string">==</span> <span class="hljs-string">'stripe'</span>

<span class="hljs-string">def</span> <span class="hljs-string">test_validate_sales_data():</span>
    <span class="hljs-string">""</span><span class="hljs-string">"Test sales data validation"</span><span class="hljs-string">""</span>
    <span class="hljs-comment"># Valid data</span>
    <span class="hljs-string">valid_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame({</span>
        <span class="hljs-attr">'sale_id':</span> [<span class="hljs-string">'1'</span>, <span class="hljs-string">'2'</span>]<span class="hljs-string">,</span>
        <span class="hljs-attr">'total_amount':</span> [<span class="hljs-number">100.0</span>, <span class="hljs-number">200.0</span>]<span class="hljs-string">,</span>
        <span class="hljs-attr">'quantity':</span> [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>]
    <span class="hljs-string">})</span>

    <span class="hljs-string">assert</span> <span class="hljs-string">validate_sales_data(valid_df)</span> <span class="hljs-string">==</span> []

    <span class="hljs-comment"># Invalid data</span>
    <span class="hljs-string">invalid_df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame({</span>
        <span class="hljs-attr">'sale_id':</span> [<span class="hljs-string">'1'</span>, <span class="hljs-string">None</span>]<span class="hljs-string">,</span>
        <span class="hljs-attr">'total_amount':</span> [<span class="hljs-number">-100.0</span>, <span class="hljs-number">200.0</span>]<span class="hljs-string">,</span>
        <span class="hljs-attr">'quantity':</span> [<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>]
    <span class="hljs-string">})</span>

    <span class="hljs-string">errors</span> <span class="hljs-string">=</span> <span class="hljs-string">validate_sales_data(invalid_df)</span>
    <span class="hljs-string">assert</span> <span class="hljs-string">len(errors)</span> <span class="hljs-string">==</span> <span class="hljs-number">3</span>  <span class="hljs-comment"># Null sale_id, negative amount, negative quantity</span>

<span class="hljs-string">def</span> <span class="hljs-string">test_integration_flow():</span>
    <span class="hljs-string">""</span><span class="hljs-string">"Test complete flow execution"</span><span class="hljs-string">""</span>
    <span class="hljs-comment"># This would use Kestra's test framework</span>
    <span class="hljs-comment"># to execute flows with mock data</span>
    <span class="hljs-string">pass</span>
</code></pre>
<h3 id="heading-integration-test-flow"><strong>Integration Test Flow</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># tests/integration/test-daily-etl.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">test-daily-etl</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.tests</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Integration</span> <span class="hljs-string">test</span> <span class="hljs-string">for</span> <span class="hljs-string">daily</span> <span class="hljs-string">ETL</span> <span class="hljs-string">pipeline</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">setup_test_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      # Generate test data
      import pandas as pd
      import numpy as np
      from datetime import datetime, timedelta
</span>
      <span class="hljs-comment"># Create test Stripe data</span>
      <span class="hljs-string">dates</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.date_range(start='2024-01-01',</span> <span class="hljs-string">end='2024-01-02',</span> <span class="hljs-string">freq='H')</span>
      <span class="hljs-string">stripe_data</span> <span class="hljs-string">=</span> []
      <span class="hljs-string">for</span> <span class="hljs-string">i,</span> <span class="hljs-string">date</span> <span class="hljs-string">in</span> <span class="hljs-string">enumerate(dates):</span>
          <span class="hljs-string">stripe_data.append({</span>
              <span class="hljs-attr">'id':</span> <span class="hljs-string">f'ch_test_{i}',</span>
              <span class="hljs-attr">'created':</span> <span class="hljs-string">int(date.timestamp()),</span>
              <span class="hljs-attr">'customer':</span> <span class="hljs-string">f'cus_test_{i</span> <span class="hljs-string">%</span> <span class="hljs-number">100</span><span class="hljs-string">}',</span>
              <span class="hljs-attr">'amount':</span> <span class="hljs-string">np.random.randint(100,</span> <span class="hljs-number">10000</span><span class="hljs-string">),</span>
              <span class="hljs-attr">'currency':</span> <span class="hljs-string">'usd'</span><span class="hljs-string">,</span>
              <span class="hljs-attr">'status':</span> <span class="hljs-string">'succeeded'</span>
          <span class="hljs-string">})</span>

      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(stripe_data)</span>
      <span class="hljs-string">df.to_parquet('test_stripe_data.parquet',</span> <span class="hljs-string">index=False)</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">run_etl_with_test_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.etl</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">daily-sales-etl</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">execution_date</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"2024-01-01"</span>
      <span class="hljs-comment"># Override extract task to use test data</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_results</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      SELECT 
        COUNT(*) as loaded_count,
        SUM(total_amount) as total_revenue
      FROM sales_fact
      WHERE batch_id = '{{ outputs.run_etl_with_test_data.executionId }}'
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">assert_test_passed</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
    <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.validate_results.rows[0].loaded_count &gt; 0 }}</span>"</span>
    <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Test failed: No data loaded"</span>
</code></pre>
<h3 id="heading-monitoring-dashboard"><strong>Monitoring Dashboard</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/monitoring/etl-dashboard.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">etl-monitoring-dashboard</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.monitoring</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">ETL</span> <span class="hljs-string">monitoring</span> <span class="hljs-string">dashboard</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">collect_metrics</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">|
      WITH pipeline_stats AS (
        SELECT 
          DATE(loaded_at) as load_date,
          COUNT(*) as records_loaded,
          SUM(total_amount) as daily_revenue,
          COUNT(DISTINCT source_system) as sources_loaded
        FROM sales_fact
        WHERE loaded_at &gt;= DATEADD(day, -30, CURRENT_DATE())
        GROUP BY DATE(loaded_at)
      ),
      error_stats AS (
        SELECT 
          DATE(start_date) as error_date,
          COUNT(*) as failed_executions,
          STRING_AGG(flow_id, ', ') as failed_flows
        FROM kestra.executions
        WHERE state = 'FAILED'
          AND start_date &gt;= DATEADD(day, -30, CURRENT_DATE())
          AND namespace LIKE 'acme.sales%'
        GROUP BY DATE(start_date)
      )
      SELECT 
        COALESCE(p.load_date, e.error_date) as date,
        p.records_loaded,
        p.daily_revenue,
        p.sources_loaded,
        COALESCE(e.failed_executions, 0) as failed_executions,
        e.failed_flows
      FROM pipeline_stats p
      FULL OUTER JOIN error_stats e ON p.load_date = e.error_date
      ORDER BY date DESC
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_dashboard</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">metrics.json:</span> <span class="hljs-string">|
        {{ outputs.collect_metrics.rows | tojson }}
</span>    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import plotly.graph_objects as go
      from plotly.subplots import make_subplots
      import json
</span>
      <span class="hljs-comment"># Load metrics</span>
      <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">json.loads('metrics.json')</span>
      <span class="hljs-string">df</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(data)</span>

      <span class="hljs-comment"># Create dashboard</span>
      <span class="hljs-string">fig</span> <span class="hljs-string">=</span> <span class="hljs-string">make_subplots(</span>
          <span class="hljs-string">rows=2,</span> <span class="hljs-string">cols=2,</span>
          <span class="hljs-string">subplot_titles=('Records</span> <span class="hljs-string">Loaded',</span> <span class="hljs-string">'Daily Revenue'</span><span class="hljs-string">,</span> 
                         <span class="hljs-string">'Sources Loaded'</span><span class="hljs-string">,</span> <span class="hljs-string">'Failed Executions'</span><span class="hljs-string">)</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Records loaded</span>
      <span class="hljs-string">fig.add_trace(</span>
          <span class="hljs-string">go.Bar(x=df['date'],</span> <span class="hljs-string">y=df['records_loaded'],</span> <span class="hljs-string">name='Records'),</span>
          <span class="hljs-string">row=1,</span> <span class="hljs-string">col=1</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Daily revenue</span>
      <span class="hljs-string">fig.add_trace(</span>
          <span class="hljs-string">go.Scatter(x=df['date'],</span> <span class="hljs-string">y=df['daily_revenue'],</span> 
                    <span class="hljs-string">mode='lines+markers',</span> <span class="hljs-string">name='Revenue'),</span>
          <span class="hljs-string">row=1,</span> <span class="hljs-string">col=2</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Sources loaded</span>
      <span class="hljs-string">fig.add_trace(</span>
          <span class="hljs-string">go.Scatter(x=df['date'],</span> <span class="hljs-string">y=df['sources_loaded'],</span>
                    <span class="hljs-string">mode='lines+markers',</span> <span class="hljs-string">name='Sources'),</span>
          <span class="hljs-string">row=2,</span> <span class="hljs-string">col=1</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Failed executions</span>
      <span class="hljs-string">fig.add_trace(</span>
          <span class="hljs-string">go.Bar(x=df['date'],</span> <span class="hljs-string">y=df['failed_executions'],</span> 
                <span class="hljs-string">name='Failures',</span> <span class="hljs-string">marker_color='red'),</span>
          <span class="hljs-string">row=2,</span> <span class="hljs-string">col=2</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Update layout</span>
      <span class="hljs-string">fig.update_layout(</span>
          <span class="hljs-string">title='ETL</span> <span class="hljs-string">Pipeline</span> <span class="hljs-string">Dashboard</span> <span class="hljs-bullet">-</span> <span class="hljs-string">Last</span> <span class="hljs-number">30</span> <span class="hljs-string">Days',</span>
          <span class="hljs-string">showlegend=False,</span>
          <span class="hljs-string">height=800</span>
      <span class="hljs-string">)</span>

      <span class="hljs-comment"># Save dashboard</span>
      <span class="hljs-string">fig.write_html('etl_dashboard.html')</span>
      <span class="hljs-string">print('Dashboard</span> <span class="hljs-attr">generated:</span> <span class="hljs-string">etl_dashboard.html')</span>
</code></pre>
<hr />
<h2 id="heading-phase-8-deployment-and-maintenance"><strong>Phase 8: Deployment and Maintenance</strong></h2>
<h3 id="heading-cicd-pipeline-for-etl-flows"><strong>CI/CD Pipeline for ETL Flows</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># .github/workflows/deploy-etl.yml</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">ETL</span> <span class="hljs-string">Flows</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'flows/**'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'scripts/**'</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">validate:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Validate</span> <span class="hljs-string">YAML</span> <span class="hljs-string">syntax</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          pip install yamllint
          yamllint -c .yamllint flows/
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Validate</span> <span class="hljs-string">with</span> <span class="hljs-string">Kestra</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          docker run --rm \
            -v $(pwd)/flows:/flows \
            kestra/kestra:latest \
            flow validate /flows
</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">validate</span>
    <span class="hljs-attr">services:</span>
      <span class="hljs-attr">postgres:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:15</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">test</span>
        <span class="hljs-attr">options:</span> <span class="hljs-string">&gt;-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
</span>    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">integration</span> <span class="hljs-string">tests</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          docker-compose -f docker-compose.test.yml up \
            --abort-on-container-exit \
            --exit-code-from tests
</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">test</span>
    <span class="hljs-attr">if:</span> <span class="hljs-string">github.ref</span> <span class="hljs-string">==</span> <span class="hljs-string">'refs/heads/main'</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Development</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          curl -X POST "https://dev-kestra.acme.com/api/v1/flows/batch" \
            -H "Authorization: Bearer ${{ secrets.KESTRA_DEV_TOKEN }}" \
            -F "files=@flows/orchestrator/daily-sales-etl.yml"
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Production</span>
        <span class="hljs-attr">if:</span> <span class="hljs-string">success()</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          curl -X POST "https://prod-kestra.acme.com/api/v1/flows/batch" \
            -H "Authorization: Bearer ${{ secrets.KESTRA_PROD_TOKEN }}" \
            -F "files=@flows/orchestrator/daily-sales-etl.yml"</span>
</code></pre>
<h3 id="heading-backup-and-recovery-strategy"><strong>Backup and Recovery Strategy</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flows/maintenance/backup-flows.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">backup-flows</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">system.maintenance</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Backup</span> <span class="hljs-string">all</span> <span class="hljs-string">flows</span> <span class="hljs-string">and</span> <span class="hljs-string">configurations</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">export_all_flows</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Export</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">".*"</span>  <span class="hljs-comment"># All namespaces</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">compress_backup</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.shell.Commands</span>
    <span class="hljs-attr">commands:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">tar</span> <span class="hljs-string">-czf</span> <span class="hljs-string">flows_backup_$(date</span> <span class="hljs-string">+%Y%m%d_%H%M%S).tar.gz</span> <span class="hljs-string">*.yml</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">upload_to_s3</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.aws.s3.Upload</span>
    <span class="hljs-attr">from:</span> <span class="hljs-string">"flows_backup_*.tar.gz"</span>
    <span class="hljs-attr">bucket:</span> <span class="hljs-string">acme-kestra-backups</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">"backups/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy/MM/dd') }}</span>/"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cleanup_old_backups</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.aws.s3.Delete</span>
    <span class="hljs-attr">bucket:</span> <span class="hljs-string">acme-kestra-backups</span>
    <span class="hljs-attr">regex:</span> <span class="hljs-string">"backups/.*\.tar\.gz"</span>
    <span class="hljs-attr">age:</span> <span class="hljs-string">P30D</span>  <span class="hljs-comment"># Delete backups older than 30 days</span>
</code></pre>
<hr />
<h2 id="heading-conclusion-your-production-ready-etl-pipeline"><strong>Conclusion: Your Production-Ready ETL Pipeline</strong></h2>
<p>Congratulations! You've just built a complete, production-grade ETL pipeline with Kestra. Let's review what you've accomplished:</p>
<h3 id="heading-what-you-built"><strong>✅ What You Built:</strong></h3>
<ol>
<li><p><strong>Multi-source extraction</strong> from Stripe, Shopify, internal DB, and Google Sheets</p>
</li>
<li><p><strong>Robust transformation</strong> with data quality checks and error handling</p>
</li>
<li><p><strong>Incremental loading</strong> to Snowflake with merge operations</p>
</li>
<li><p><strong>Comprehensive monitoring</strong> with alerts and dashboards</p>
</li>
<li><p><strong>Testing framework</strong> for validation and regression testing</p>
</li>
<li><p><strong>CI/CD pipeline</strong> for automated deployment</p>
</li>
</ol>
<h3 id="heading-key-success-factors"><strong>🎯 Key Success Factors:</strong></h3>
<ol>
<li><p><strong>Modular design</strong>: Each component is independent and testable</p>
</li>
<li><p><strong>Error resilience</strong>: Graceful handling of API failures, data issues</p>
</li>
<li><p><strong>Observability</strong>: Complete visibility into pipeline execution</p>
</li>
<li><p><strong>Maintainability</strong>: Clear documentation and version control</p>
</li>
<li><p><strong>Scalability</strong>: Parallel processing for large datasets</p>
</li>
</ol>
<h3 id="heading-next-steps-to-production"><strong>🚀 Next Steps to Production:</strong></h3>
<ol>
<li><p><strong>Performance Tuning</strong>:</p>
<pre><code class="lang-yaml"> <span class="hljs-comment"># Monitor and optimize</span>
 <span class="hljs-string">kubectl</span> <span class="hljs-string">top</span> <span class="hljs-string">pods</span> <span class="hljs-string">-n</span> <span class="hljs-string">kestra</span>
 <span class="hljs-comment"># Check slow queries in Snowflake</span>
</code></pre>
</li>
<li><p><strong>Security Hardening</strong>:</p>
<ul>
<li><p>Rotate API keys monthly</p>
</li>
<li><p>Enable audit logging</p>
</li>
<li><p>Implement network policies</p>
</li>
<li><p>Regular security scans</p>
</li>
</ul>
</li>
<li><p><strong>Cost Optimization</strong>:</p>
<ul>
<li><p>Right-size compute resources</p>
</li>
<li><p>Implement data retention policies</p>
</li>
<li><p>Monitor cloud spend</p>
</li>
</ul>
</li>
<li><p><strong>Disaster Recovery</strong>:</p>
<pre><code class="lang-yaml"> <span class="hljs-comment"># Regular backup testing</span>
 <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">test_restore</span>
   <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
   <span class="hljs-attr">namespace:</span> <span class="hljs-string">system.dr</span>
   <span class="hljs-attr">flowId:</span> <span class="hljs-string">test-backup-restore</span>
   <span class="hljs-attr">schedule:</span> <span class="hljs-string">"0 0 * * 0"</span>  <span class="hljs-comment"># Weekly</span>
</code></pre>
</li>
</ol>
<h3 id="heading-measuring-success"><strong>📈 Measuring Success:</strong></h3>
<p>Track these KPIs:</p>
<ol>
<li><p><strong>Pipeline reliability</strong>: &gt;99.5% success rate</p>
</li>
<li><p><strong>Data freshness</strong>: &lt;4 hours from source to warehouse</p>
</li>
<li><p><strong>Data quality</strong>: &lt;1% error rate in transformations</p>
</li>
<li><p><strong>Cost efficiency</strong>: &lt;$0.01 per 1000 records processed</p>
</li>
<li><p><strong>Team productivity</strong>: &lt;1 hour to add new data source</p>
</li>
</ol>
<h3 id="heading-future-enhancements"><strong>🔮 Future Enhancements:</strong></h3>
<ol>
<li><p><strong>Real-time processing</strong>: Add streaming with Kafka</p>
</li>
<li><p><strong>Machine learning</strong>: Integrate model training pipelines</p>
</li>
<li><p><strong>Data governance</strong>: Implement data lineage and catalog</p>
</li>
<li><p><strong>Self-service</strong>: Build UI for business users to trigger flows</p>
</li>
<li><p><strong>Cross-region replication</strong>: For global redundancy</p>
</li>
</ol>
<hr />
<h2 id="heading-your-challenge"><strong>Your Challenge:</strong></h2>
<p>Now it's your turn! Pick one improvement to implement:</p>
<ol>
<li><p><strong>Add a new data source</strong> (e.g., Salesforce, HubSpot)</p>
</li>
<li><p><strong>Implement data masking</strong> for PII in test environments</p>
</li>
<li><p><strong>Build a real-time alert</strong> for revenue anomalies</p>
</li>
<li><p><strong>Create a self-healing pipeline</strong> that auto-retries with different strategies</p>
</li>
<li><p><strong>Add predictive scaling</strong> based on data volume patterns</p>
</li>
</ol>
<p>Share what you build in the comments below!</p>
<hr />
<p><strong>Remember</strong>: The best ETL pipeline isn't the most complex—it's the most reliable. Start simple, monitor everything, and iterate based on data.</p>
<p>In the next article, we'll dive into <strong>Advanced Workflow Patterns</strong> in Kestra, where we'll explore parallel processing, dynamic workflows, and machine learning pipelines.</p>
<p><strong>Before the next article, try:</strong></p>
<ol>
<li><p>Deploy your ETL pipeline to a cloud environment</p>
</li>
<li><p>Set up monitoring alerts for your critical flows</p>
</li>
<li><p>Run a backfill for the last 7 days of data</p>
</li>
<li><p>Document your pipeline architecture for your team</p>
</li>
</ol>
<p>Happy data engineering! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Article 3: Understanding Kestra's Architecture: Flows, Tasks, and Namespaces]]></title><description><![CDATA[Building Blocks of Declarative Orchestration.
Introduction: The Power of Simplicity
Imagine trying to build a house without understanding bricks, beams, and blueprints. That's what using an orchestration tool without understanding its core concepts f...]]></description><link>https://techwasti.com/article-3-understanding-kestras-architecture-flows-tasks-and-namespaces</link><guid isPermaLink="true">https://techwasti.com/article-3-understanding-kestras-architecture-flows-tasks-and-namespaces</guid><category><![CDATA[#Kestra]]></category><category><![CDATA[dataengineering]]></category><category><![CDATA[ETL]]></category><category><![CDATA[#declarative-pipeline]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Sun, 08 Feb 2026 06:56:46 GMT</pubDate><content:encoded><![CDATA[<p><strong>Building Blocks of Declarative Orchestration.</strong></p>
<h2 id="heading-introduction-the-power-of-simplicity"><strong>Introduction: The Power of Simplicity</strong></h2>
<p>Imagine trying to build a house without understanding bricks, beams, and blueprints. That's what using an orchestration tool without understanding its core concepts feels like. Today, we'll transform you from a casual user to an architect who understands every component of Kestra's structure.</p>
<p><strong>By the end of this article, you'll be able to:</strong></p>
<ul>
<li><p>Design complex workflows with confidence</p>
</li>
<li><p>Choose the right task for every job</p>
</li>
<li><p>Organize your projects for maximum maintainability</p>
</li>
<li><p>Debug issues by understanding the underlying architecture</p>
</li>
<li><p>Optimize performance at the structural level</p>
</li>
</ul>
<hr />
<h2 id="heading-core-concept-1-namespaces-your-organizational-foundation"><strong>Core Concept 1: Namespaces - Your Organizational Foundation</strong></h2>
<h3 id="heading-what-are-namespaces"><strong>What Are Namespaces?</strong></h3>
<p>Think of namespaces as <strong>virtual folders</strong> or <strong>projects</strong> in Kestra. They're not just organizational fluff—they're the backbone of security, access control, and workflow management.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># This isn't just a label, it's an organizational system</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">finance.europe.reports</span>
</code></pre>
<h3 id="heading-why-namespaces-matter-more-than-you-think"><strong>Why Namespaces Matter More Than You Think</strong></h3>
<p><strong>Real-world analogy</strong>: If Kestra were a company:</p>
<ul>
<li><p><strong>Namespace</strong> = Department (Finance, Marketing, Engineering)</p>
</li>
<li><p><strong>Flow</strong> = Project within that department</p>
</li>
<li><p><strong>Task</strong> = Individual task in the project</p>
</li>
<li><p><strong>Execution</strong> = Project run</p>
</li>
</ul>
<h3 id="heading-namespace-hierarchy-best-practices"><strong>Namespace Hierarchy Best Practices</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD: Flat structure</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">sales_report_2024</span>

<span class="hljs-comment"># GOOD: Hierarchical structure</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">sales</span>
<span class="hljs-comment"># or better:</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">sales.region.europe.reports</span>
<span class="hljs-comment"># or even:</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">sales.region.europe.reports.monthly</span>
</code></pre>
<p><strong>Recommended Structure:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-string">company.department.region.function.frequency</span>
<span class="hljs-string">└──</span> <span class="hljs-string">acme.finance.europe.reports.monthly</span>
<span class="hljs-string">└──</span> <span class="hljs-string">acme.marketing.us.campaigns.daily</span>
<span class="hljs-string">└──</span> <span class="hljs-string">acme.engineering.global.ci.cd.triggered</span>
</code></pre>
<h3 id="heading-practical-implementation"><strong>Practical Implementation</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># namespace-structure.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">create-namespaces</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">system.admin</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Create</span> <span class="hljs-string">organizational</span> <span class="hljs-string">namespace</span> <span class="hljs-string">structure</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">create-finance-namespace</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.namespace.CreateNamespace</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.finance</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Finance department workflows"</span>
    <span class="hljs-attr">variables:</span>
      <span class="hljs-attr">department:</span> <span class="hljs-string">finance</span>
      <span class="hljs-attr">sla:</span> <span class="hljs-string">business_hours</span>
      <span class="hljs-attr">owner:</span> <span class="hljs-string">finance-team@acme.com</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">create-finance-subnamespaces</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
    <span class="hljs-attr">value:</span> [<span class="hljs-string">"reports"</span>, <span class="hljs-string">"reconciliation"</span>, <span class="hljs-string">"compliance"</span>, <span class="hljs-string">"forecasting"</span>]
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">create-subnamespace-{{</span> <span class="hljs-string">task.value</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.namespace.CreateNamespace</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">"acme.finance.<span class="hljs-template-variable">{{ task.value }}</span>"</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">"Finance <span class="hljs-template-variable">{{ task.value }}</span> workflows"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">set-permissions</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.namespace.UpdateNamespace</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.finance.reports</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">username:</span> <span class="hljs-string">alice@acme.com</span>
        <span class="hljs-attr">permission:</span> <span class="hljs-string">EXECUTE</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">username:</span> <span class="hljs-string">bob@acme.com</span>
        <span class="hljs-attr">permission:</span> <span class="hljs-string">READ</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">group:</span> <span class="hljs-string">finance-managers</span>
        <span class="hljs-attr">permission:</span> <span class="hljs-string">OWNER</span>
</code></pre>
<h3 id="heading-namespace-configuration-options"><strong>Namespace Configuration Options</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># Advanced namespace configuration</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">configure-namespace</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">system.config</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Configure</span> <span class="hljs-string">namespace</span> <span class="hljs-string">with</span> <span class="hljs-string">advanced</span> <span class="hljs-string">settings</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">create-namespace-with-advanced-config</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.namespace.CreateNamespace</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.sensitive</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Namespace for sensitive data processing"</span>
    <span class="hljs-attr">variables:</span>
      <span class="hljs-comment"># Custom metadata</span>
      <span class="hljs-attr">data_classification:</span> <span class="hljs-string">PII</span>
      <span class="hljs-attr">retention_days:</span> <span class="hljs-number">30</span>
      <span class="hljs-attr">backup_frequency:</span> <span class="hljs-string">daily</span>
      <span class="hljs-attr">compliance:</span> <span class="hljs-string">GDPR</span>

    <span class="hljs-comment"># Task defaults for this namespace</span>
    <span class="hljs-attr">taskDefaults:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
        <span class="hljs-attr">values:</span>
          <span class="hljs-attr">taskRunner:</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">1Gi</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">500m</span>
          <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT30M</span>

    <span class="hljs-comment"># Flow defaults</span>
    <span class="hljs-attr">flowDefaults:</span>
      <span class="hljs-attr">triggers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Schedule</span>
          <span class="hljs-attr">disabled:</span> <span class="hljs-literal">false</span>
      <span class="hljs-attr">logging:</span>
        <span class="hljs-attr">level:</span> <span class="hljs-string">INFO</span>
</code></pre>
<h3 id="heading-the-hidden-power-namespace-variables"><strong>The Hidden Power: Namespace Variables</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># Using namespace variables</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">use-namespace-vars</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.finance.reports</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Demonstrates</span> <span class="hljs-string">namespace</span> <span class="hljs-string">variables</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">report_date</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() }}</span>"</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">log-config</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">|
      Namespace: {{ namespace.id }}
      Variables: {{ namespace.variables | tojson }}
      SLA: {{ namespace.variables.sla }}
      Owner: {{ namespace.variables.owner }}
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process-report</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">&gt;
      Processing {{ namespace.variables.department }} report
      for {{ inputs.report_date | date('yyyy-MM-dd') }}
      with retention {{ namespace.variables.retention_days }} days</span>
</code></pre>
<h3 id="heading-common-namespace-anti-patterns"><strong>Common Namespace Anti-Patterns</strong></h3>
<p><strong>Anti-Pattern 1: Too Many Namespaces</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Don't do this:</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.daily.report.january.2024.region.europe</span>
<span class="hljs-comment"># Result: Unmanageable hierarchy, permission nightmares</span>
</code></pre>
<p><strong>Anti-Pattern 2: No Structure</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Don't do this:</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">report_for_bob</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">alice_special_project</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">urgent_thing</span>
<span class="hljs-comment"># Result: Chaos, no discoverability</span>
</code></pre>
<p><strong>Anti-Pattern 3: Using Namespaces as Environments</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Instead of:</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">dev.sales.report</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">prod.sales.report</span>

<span class="hljs-comment"># Consider:</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">sales.report</span>
<span class="hljs-comment"># Use flow labels or variables for environment:</span>
<span class="hljs-attr">labels:</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">dev</span>
  <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>
</code></pre>
<hr />
<h2 id="heading-core-concept-2-flows-your-workflow-blueprint"><strong>Core Concept 2: Flows - Your Workflow Blueprint</strong></h2>
<h3 id="heading-anatomy-of-a-flow"><strong>Anatomy of a Flow</strong></h3>
<p>A flow is a <strong>declarative blueprint</strong> for your workflow. Let's dissect one:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># flow-anatomy.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">data-pipeline</span>                     <span class="hljs-comment"># Unique identifier within namespace</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.engineering</span>      <span class="hljs-comment"># Organizational container</span>
<span class="hljs-attr">revision:</span> <span class="hljs-number">3</span>                           <span class="hljs-comment"># Version (auto-managed)</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">"ETL pipeline for customer data"</span>  <span class="hljs-comment"># Human-readable description</span>
<span class="hljs-attr">labels:</span>                               <span class="hljs-comment"># Metadata for filtering/searching</span>
  <span class="hljs-attr">team:</span> <span class="hljs-string">data-engineering</span>
  <span class="hljs-attr">priority:</span> <span class="hljs-string">high</span>
  <span class="hljs-attr">data-sensitive:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">cost-center:</span> <span class="hljs-string">marketing</span>

<span class="hljs-attr">tasks:</span>                                <span class="hljs-comment"># The actual workflow steps</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract</span>
    <span class="hljs-comment"># Task configuration...</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform</span>
    <span class="hljs-comment"># Task configuration...</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load</span>
    <span class="hljs-comment"># Task configuration...</span>

<span class="hljs-attr">triggers:</span>                            <span class="hljs-comment"># When to run automatically</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">schedule</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Schedule</span>
    <span class="hljs-attr">cron:</span> <span class="hljs-string">"0 2 * * *"</span>

<span class="hljs-attr">variables:</span>                           <span class="hljs-comment"># Flow-level variables</span>
  <span class="hljs-attr">batch_size:</span> <span class="hljs-number">1000</span>
  <span class="hljs-attr">target_table:</span> <span class="hljs-string">customers_staging</span>

<span class="hljs-attr">timeout:</span> <span class="hljs-string">PT1H</span>                        <span class="hljs-comment"># Maximum execution time</span>
<span class="hljs-attr">disabled:</span> <span class="hljs-literal">false</span>                      <span class="hljs-comment"># Enable/disable flow</span>
</code></pre>
<h3 id="heading-flow-lifecycle-from-yaml-to-execution"><strong>Flow Lifecycle: From YAML to Execution</strong></h3>
<p>Understanding this lifecycle is crucial for debugging:</p>
<pre><code class="lang-yaml"><span class="hljs-number">1</span><span class="hljs-string">.</span> <span class="hljs-string">Definition</span> <span class="hljs-string">(YAML)</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">2</span><span class="hljs-string">.</span> <span class="hljs-string">Parsing</span> <span class="hljs-string">&amp;</span> <span class="hljs-string">Validation</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">3</span><span class="hljs-string">.</span> <span class="hljs-string">Storage</span> <span class="hljs-string">(PostgreSQL)</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">4</span><span class="hljs-string">.</span> <span class="hljs-string">Trigger</span> <span class="hljs-string">Detection</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">5</span><span class="hljs-string">.</span> <span class="hljs-string">Execution</span> <span class="hljs-string">Instantiation</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">6</span><span class="hljs-string">.</span> <span class="hljs-string">Task</span> <span class="hljs-string">Execution</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">7</span><span class="hljs-string">.</span> <span class="hljs-string">State</span> <span class="hljs-string">Management</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">8</span><span class="hljs-string">.</span> <span class="hljs-string">Completion/Error</span> <span class="hljs-string">Handling</span>
</code></pre>
<h3 id="heading-advanced-flow-patterns"><strong>Advanced Flow Patterns</strong></h3>
<h4 id="heading-pattern-1-modular-flows-with-subflows"><strong>Pattern 1: Modular Flows with Subflows</strong></h4>
<pre><code class="lang-yaml"><span class="hljs-comment"># main-flow.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">main-orchestration</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">call-subflow</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.utilities</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">data-validation</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">input_file</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.download.uri }}</span>"</span>
    <span class="hljs-attr">receive:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">validation_report</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">continue-processing</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">"Validation result: <span class="hljs-template-variable">{{ outputs.call-subflow.outputs.validation_report }}</span>"</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.call-subflow.state.current == 'SUCCESS' }}</span>"</span>
</code></pre>
<h4 id="heading-pattern-2-dynamic-flow-generation"><strong>Pattern 2: Dynamic Flow Generation</strong></h4>
<pre><code class="lang-yaml"><span class="hljs-comment"># generate-flows.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">flow-generator</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.system</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">get-regions</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">"SELECT region_id, region_name FROM sales_regions"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">create-region-flows</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachParallel</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.get-regions.rows }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">create-flow-{{</span> <span class="hljs-string">task.value.region_id</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Create</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.region</span>
        <span class="hljs-attr">flow:</span>
          <span class="hljs-attr">id:</span> <span class="hljs-string">process_{{</span> <span class="hljs-string">task.value.region_name</span> <span class="hljs-string">|</span> <span class="hljs-string">lower</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.sales.region</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">"Process sales for <span class="hljs-template-variable">{{ task.value.region_name }}</span>"</span>
          <span class="hljs-attr">tasks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract</span>
              <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>
              <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.acme.com/sales/<span class="hljs-template-variable">{{ task.value.region_id }}</span>/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy-MM-dd') }}</span>"</span>
</code></pre>
<h4 id="heading-pattern-3-flow-templating"><strong>Pattern 3: Flow Templating</strong></h4>
<pre><code class="lang-yaml"><span class="hljs-comment"># flow-template.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">report-template</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.templates</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Template</span> <span class="hljs-string">for</span> <span class="hljs-string">all</span> <span class="hljs-string">report</span> <span class="hljs-string">flows</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">report_name:</span> <span class="hljs-string">REQUIRED</span>
  <span class="hljs-attr">source_url:</span> <span class="hljs-string">REQUIRED</span>
  <span class="hljs-attr">target_database:</span> <span class="hljs-string">"data_warehouse"</span>
  <span class="hljs-attr">schedule_cron:</span> <span class="hljs-string">"0 2 * * *"</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.source_url }}</span>/<span class="hljs-template-variable">{{ execution.startDate | date('yyyy-MM-dd') }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      # Transform logic for {{ vars.report_name }}
      print("Processing {{ vars.report_name }}")
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Load</span>
    <span class="hljs-attr">table:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.report_name | lower }}</span>_reports"</span>
    <span class="hljs-attr">from:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform.outputFiles['output.parquet'] }}</span>"</span>

<span class="hljs-attr">triggers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">schedule</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Schedule</span>
    <span class="hljs-attr">cron:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.schedule_cron }}</span>"</span>
</code></pre>
<h3 id="heading-flow-metadata-and-best-practices"><strong>Flow Metadata and Best Practices</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># flow-with-metadata.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">production-pipeline</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.production</span>
<span class="hljs-attr">revision:</span> <span class="hljs-number">1</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">|
  # Production Data Pipeline
</span>
  <span class="hljs-comment">## Purpose</span>
  <span class="hljs-string">Processes</span> <span class="hljs-string">customer</span> <span class="hljs-string">transactions</span> <span class="hljs-string">for</span> <span class="hljs-string">billing.</span>

  <span class="hljs-comment">## Owner</span>
  <span class="hljs-string">Data</span> <span class="hljs-string">Engineering</span> <span class="hljs-string">Team</span> <span class="hljs-string">(data-team@acme.com)</span>

  <span class="hljs-comment">## SLA</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Must</span> <span class="hljs-string">complete</span> <span class="hljs-string">within</span> <span class="hljs-number">2</span> <span class="hljs-string">hours</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Runs</span> <span class="hljs-string">daily</span> <span class="hljs-string">at</span> <span class="hljs-number">2</span> <span class="hljs-string">AM</span> <span class="hljs-string">UTC</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Alert</span> <span class="hljs-string">on-call</span> <span class="hljs-string">if</span> <span class="hljs-string">fails</span>

  <span class="hljs-comment">## Data Flow</span>
  <span class="hljs-number">1</span><span class="hljs-string">.</span> <span class="hljs-string">Extract</span> <span class="hljs-string">from</span> <span class="hljs-string">Payment</span> <span class="hljs-string">API</span>
  <span class="hljs-number">2</span><span class="hljs-string">.</span> <span class="hljs-string">Validate</span> <span class="hljs-string">transactions</span>
  <span class="hljs-number">3</span><span class="hljs-string">.</span> <span class="hljs-string">Enrich</span> <span class="hljs-string">with</span> <span class="hljs-string">customer</span> <span class="hljs-string">data</span>
  <span class="hljs-number">4</span><span class="hljs-string">.</span> <span class="hljs-string">Load</span> <span class="hljs-string">to</span> <span class="hljs-string">Data</span> <span class="hljs-string">Warehouse</span>
  <span class="hljs-number">5</span><span class="hljs-string">.</span> <span class="hljs-string">Generate</span> <span class="hljs-string">reconciliation</span> <span class="hljs-string">report</span>

<span class="hljs-attr">documentation:</span> <span class="hljs-string">|
  ## Technical Details
</span>
  <span class="hljs-comment">### Dependencies</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Payment</span> <span class="hljs-string">API</span> <span class="hljs-string">v2.1</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Customer</span> <span class="hljs-string">Service</span> <span class="hljs-string">v1.3</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Snowflake</span> <span class="hljs-string">DW</span>

  <span class="hljs-comment">### Error Handling</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Retry</span> <span class="hljs-number">3</span> <span class="hljs-string">times</span> <span class="hljs-string">on</span> <span class="hljs-string">API</span> <span class="hljs-string">failures</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Dead</span> <span class="hljs-string">letter</span> <span class="hljs-string">queue</span> <span class="hljs-string">for</span> <span class="hljs-string">invalid</span> <span class="hljs-string">records</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Slack</span> <span class="hljs-string">alert</span> <span class="hljs-string">on</span> <span class="hljs-string">permanent</span> <span class="hljs-string">failure</span>

  <span class="hljs-comment">### Performance</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Processes</span> <span class="hljs-string">~1M</span> <span class="hljs-string">records/day</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Average runtime:</span> <span class="hljs-number">45</span> <span class="hljs-string">minutes</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Peak memory:</span> <span class="hljs-string">2GB</span>

<span class="hljs-attr">labels:</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">production</span>
  <span class="hljs-attr">tier:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">team:</span> <span class="hljs-string">data-engineering</span>
  <span class="hljs-attr">cost-center:</span> <span class="hljs-string">billing</span>
  <span class="hljs-attr">compliance:</span> <span class="hljs-string">pci-dss</span>
  <span class="hljs-attr">data-retention:</span> <span class="hljs-number">7</span><span class="hljs-string">-years</span>

<span class="hljs-comment"># Audit trail</span>
<span class="hljs-attr">created:</span> <span class="hljs-number">2024-01-15T10:30:00Z</span>
<span class="hljs-attr">updated:</span> <span class="hljs-number">2024-01-20T14:45:00Z</span>
<span class="hljs-attr">updatedBy:</span> <span class="hljs-string">alice@acme.com</span>
</code></pre>
<h3 id="heading-flow-versioning-and-deployment-strategies"><strong>Flow Versioning and Deployment Strategies</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># deployment-strategy.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">deploy-flow-version</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.deployment</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">flow_id</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">namespace</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">yaml_content</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">deployment_strategy</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">STRING</span>
    <span class="hljs-attr">choices:</span> [<span class="hljs-string">blue-green</span>, <span class="hljs-string">canary</span>, <span class="hljs-string">immediate</span>]
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">immediate</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate-flow</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Validate</span>
    <span class="hljs-attr">flow:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.yaml_content }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">get-current-flow</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Get</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.namespace }}</span>"</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.flow_id }}</span>"</span>
    <span class="hljs-attr">ignoreMissing:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">deploy-immediate</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Update</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.namespace }}</span>"</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.flow_id }}</span>"</span>
    <span class="hljs-attr">flow:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.yaml_content }}</span>"</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.deployment_strategy == 'immediate' }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">deploy-blue-green</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Create</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.namespace }}</span>"</span>
    <span class="hljs-attr">flow:</span>
      <span class="hljs-attr">id:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.flow_id }}</span>-blue"</span>
      <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.namespace }}</span>"</span>
      <span class="hljs-comment"># ... flow content with -blue suffix</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.deployment_strategy == 'blue-green' }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-core-concept-3-tasks-the-workhorses-of-your-workflow"><strong>Core Concept 3: Tasks - The Workhorses of Your Workflow</strong></h2>
<h3 id="heading-task-taxonomy-understanding-the-hierarchy"><strong>Task Taxonomy: Understanding the Hierarchy</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-string">Task</span> <span class="hljs-string">Types</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Core</span> <span class="hljs-string">Tasks</span> <span class="hljs-string">(Built-in)</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Control</span> <span class="hljs-string">Flow</span>
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Parallel</span>
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Sequential</span>
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Each</span> <span class="hljs-string">(Loop)</span>
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">Conditional</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Data</span> <span class="hljs-string">Movement</span>
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">HTTP</span>
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Files</span>
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">Databases</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">Utilities</span>
<span class="hljs-string">│</span>       <span class="hljs-string">├──</span> <span class="hljs-string">Logging</span>
<span class="hljs-string">│</span>       <span class="hljs-string">├──</span> <span class="hljs-string">Debugging</span>
<span class="hljs-string">│</span>       <span class="hljs-string">└──</span> <span class="hljs-string">Notifications</span>
<span class="hljs-string">│</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Script</span> <span class="hljs-string">Tasks</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Python</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Bash</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Node.js</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">R</span>
<span class="hljs-string">│</span>
<span class="hljs-string">├──</span> <span class="hljs-string">Plugin</span> <span class="hljs-string">Tasks</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Cloud</span> <span class="hljs-string">(AWS,</span> <span class="hljs-string">GCP,</span> <span class="hljs-string">Azure)</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Databases</span> <span class="hljs-string">(PostgreSQL,</span> <span class="hljs-string">Snowflake,</span> <span class="hljs-string">BigQuery)</span>
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-string">Messaging</span> <span class="hljs-string">(Kafka,</span> <span class="hljs-string">RabbitMQ)</span>
<span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-string">Specialized</span> <span class="hljs-string">(dbt,</span> <span class="hljs-string">Great</span> <span class="hljs-string">Expectations)</span>
<span class="hljs-string">│</span>
<span class="hljs-string">└──</span> <span class="hljs-string">Custom</span> <span class="hljs-string">Tasks</span>
    <span class="hljs-string">└──</span> <span class="hljs-string">Java/Scala</span> <span class="hljs-string">plugins</span>
</code></pre>
<h3 id="heading-task-anatomy-every-task-explained"><strong>Task Anatomy: Every Task Explained</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">download_file</span>                    <span class="hljs-comment"># Required: Unique identifier</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>  <span class="hljs-comment"># Required: Task type</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Download sales data"</span>   <span class="hljs-comment"># Optional: Human description</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.example.com/data"</span>  <span class="hljs-comment"># Required: Type-specific params</span>

    <span class="hljs-comment"># Execution Control</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT5M</span>                        <span class="hljs-comment"># Max execution time</span>
    <span class="hljs-attr">retry:</span>                               <span class="hljs-comment"># Retry configuration</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
      <span class="hljs-attr">maxAttempt:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">delay:</span> <span class="hljs-string">PT10S</span>

    <span class="hljs-comment"># Error Handling</span>
    <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">false</span>                  <span class="hljs-comment"># Continue on failure?</span>
    <span class="hljs-attr">fatalError:</span> [<span class="hljs-string">"CONNECTION_ERROR"</span>]     <span class="hljs-comment"># Specific errors that stop flow</span>

    <span class="hljs-comment"># Resource Management</span>
    <span class="hljs-attr">taskRunner:</span>                          <span class="hljs-comment"># How to run the task</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">512Mi</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">250m</span>

    <span class="hljs-comment"># Input/Output</span>
    <span class="hljs-attr">inputFiles:</span>                          <span class="hljs-comment"># Files available to task</span>
      <span class="hljs-attr">config.json:</span> <span class="hljs-string">|
        {"key": "value"}
</span>    <span class="hljs-attr">outputFiles:</span>                         <span class="hljs-comment"># Files produced by task</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"*.parquet"</span>

    <span class="hljs-comment"># Dependencies</span>
    <span class="hljs-attr">dependsOn:</span>                           <span class="hljs-comment"># Wait for these tasks</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">validate_config</span>
    <span class="hljs-attr">send:</span>                                <span class="hljs-comment"># Data to pass</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">url</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.config_reader.api_url }}</span>"</span>

    <span class="hljs-comment"># Metadata</span>
    <span class="hljs-attr">labels:</span>                              <span class="hljs-comment"># Task-specific labels</span>
      <span class="hljs-attr">data-sensitive:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">cost-center:</span> <span class="hljs-string">marketing</span>

    <span class="hljs-comment"># Advanced</span>
    <span class="hljs-attr">before:</span> []                           <span class="hljs-comment"># Hooks</span>
    <span class="hljs-attr">after:</span> []
    <span class="hljs-attr">workerGroup:</span> <span class="hljs-string">heavy-lifting</span>           <span class="hljs-comment"># Specific worker group</span>
</code></pre>
<h3 id="heading-task-execution-states"><strong>Task Execution States</strong></h3>
<p>Understanding these states is crucial for debugging:</p>
<h3 id="heading-advanced-task-patterns"><strong>Advanced Task Patterns</strong></h3>
<h4 id="heading-pattern-1-dynamic-task-generation"><strong>Pattern 1: Dynamic Task Generation</strong></h4>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">get_file_list</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.example.com/files"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_files</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachParallel</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.get_file_list.body | jq('.files[]') }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"process_<span class="hljs-template-variable">{{ taskloop.index }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task.value.url }}</span>"</span>
        <span class="hljs-attr">outputFile:</span> <span class="hljs-string">"file_<span class="hljs-template-variable">{{ taskloop.index }}</span>.csv"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"transform_<span class="hljs-template-variable">{{ taskloop.index }}</span>"</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
        <span class="hljs-attr">inputFiles:</span>
          <span class="hljs-attr">input.csv:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs['process_' + taskloop.index].uri }}</span>"</span>
        <span class="hljs-attr">script:</span> <span class="hljs-string">|
          import pandas as pd
          df = pd.read_csv('input.csv')
          df.to_parquet('output.parquet')
</span>        <span class="hljs-attr">dependsOn:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"process_<span class="hljs-template-variable">{{ taskloop.index }}</span>"</span>
</code></pre>
<h4 id="heading-pattern-2-task-templates-with-macros"><strong>Pattern 2: Task Templates with Macros</strong></h4>
<pre><code class="lang-yaml"><span class="hljs-comment"># Define reusable task patterns</span>
<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">retry_config:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
    <span class="hljs-attr">maxAttempt:</span> <span class="hljs-number">3</span>
    <span class="hljs-attr">delay:</span> <span class="hljs-string">PT10S</span>

  <span class="hljs-attr">resource_config:</span>
    <span class="hljs-attr">taskRunner:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">1Gi</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-string">500m</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">api_call_template</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ api_endpoint }}</span>"</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">POST</span>
    <span class="hljs-attr">headers:</span>
      <span class="hljs-attr">Authorization:</span> <span class="hljs-string">"Bearer <span class="hljs-template-variable">{{ api_token }}</span>"</span>
    <span class="hljs-attr">retry:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.retry_config }}</span>"</span>
    <span class="hljs-attr">taskRunner:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.resource_config }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">specific_api_call</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Request</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.example.com/users"</span>
    <span class="hljs-comment"># Inherit from template</span>
    <span class="hljs-string">&lt;&lt;:</span> <span class="hljs-meta">*api_call_template</span>
    <span class="hljs-comment"># Override specific values</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">GET</span>
</code></pre>
<h4 id="heading-pattern-3-task-composition"><strong>Pattern 3: Task Composition</strong></h4>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">composite_task</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Sequential</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">step1</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
        <span class="hljs-attr">message:</span> <span class="hljs-string">"Step 1"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">step2</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">"Step 2 output"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">step3</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
        <span class="hljs-attr">message:</span> <span class="hljs-string">"Step 3 with output: <span class="hljs-template-variable">{{ outputs.step2.value }}</span>"</span>
</code></pre>
<h3 id="heading-task-communication-the-secret-sauce"><strong>Task Communication: The Secret Sauce</strong></h3>
<p>Tasks communicate through <strong>inputs and outputs</strong>. Understanding this is key to building complex workflows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">producer</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">"Hello from producer"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">consumer_direct</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">"Direct access: <span class="hljs-template-variable">{{ outputs.producer.value }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">consumer_with_send</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">"Forwarded: <span class="hljs-template-variable">{{ taskrun.value }}</span>"</span>
    <span class="hljs-attr">send:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">producer</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import json
      data = {{ outputs.producer.value | tojson }}
      result = {"transformed": data.upper()}
      print(json.dumps(result))
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">use_transformed</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">&gt;</span>
      <span class="hljs-attr">Transformed:</span> {{ <span class="hljs-string">outputs.transform_data.vars.result</span> <span class="hljs-string">|</span> <span class="hljs-string">fromjson</span> }}
</code></pre>
<h3 id="heading-task-error-handling-strategies"><strong>Task Error Handling Strategies</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">risky_operation</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://unreliable-api.com/data"</span>

    <span class="hljs-comment"># Strategy 1: Retry</span>
    <span class="hljs-attr">retry:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
      <span class="hljs-attr">maxAttempt:</span> <span class="hljs-number">5</span>
      <span class="hljs-attr">delay:</span> <span class="hljs-string">PT30S</span>
      <span class="hljs-attr">maxDelay:</span> <span class="hljs-string">PT5M</span>

    <span class="hljs-comment"># Strategy 2: Allow failure with fallback</span>
    <span class="hljs-attr">allowFailure:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">fallback</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.debug.Return</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">"Using cached data"</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.risky_operation.state.current == 'FAILED' }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">continue_with_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Switch</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.risky_operation.state.current }}</span>"</span>
    <span class="hljs-attr">cases:</span>
      <span class="hljs-attr">SUCCESS:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">use_api_data</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
          <span class="hljs-attr">message:</span> <span class="hljs-string">"Using API data"</span>
      <span class="hljs-attr">FAILED:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">use_fallback_data</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.log.Log</span>
          <span class="hljs-attr">message:</span> <span class="hljs-string">"Using fallback data"</span>

  <span class="hljs-comment"># Strategy 3: Dead letter queue</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">dead_letter_queue</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">system.errors</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">handle-failure</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">false</span>  <span class="hljs-comment"># Don't wait, fire and forget</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">error_details</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.risky_operation }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">flow_execution_id</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.risky_operation.state.current == 'FAILED' }}</span>"</span>
</code></pre>
<h3 id="heading-task-performance-optimization"><strong>Task Performance Optimization</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">optimized_task</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
    <span class="hljs-attr">sql:</span> <span class="hljs-string">"SELECT * FROM large_table"</span>

    <span class="hljs-comment"># Optimization 1: Resource allocation</span>
    <span class="hljs-attr">taskRunner:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">4Gi</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-number">2</span>

    <span class="hljs-comment"># Optimization 2: Parallelism</span>
    <span class="hljs-attr">maxParallel:</span> <span class="hljs-number">4</span>

    <span class="hljs-comment"># Optimization 3: Caching</span>
    <span class="hljs-attr">cache:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">ttl:</span> <span class="hljs-string">PT1H</span>
      <span class="hljs-attr">inputs:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">sql</span>

    <span class="hljs-comment"># Optimization 4: Chunking</span>
    <span class="hljs-attr">fetchSize:</span> <span class="hljs-number">10000</span>
    <span class="hljs-attr">chunkSize:</span> <span class="hljs-number">1000</span>

    <span class="hljs-comment"># Optimization 5: Timeouts</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">PT30M</span>
    <span class="hljs-attr">connectionTimeout:</span> <span class="hljs-string">PT5M</span>
    <span class="hljs-attr">readTimeout:</span> <span class="hljs-string">PT10M</span>
</code></pre>
<hr />
<h2 id="heading-putting-it-all-together-real-world-example"><strong>Putting It All Together: Real-World Example</strong></h2>
<p>Let's build a complete, production-ready pipeline using all concepts:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># production-data-pipeline.yml</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">customer-lifetime-value</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.marketing.analytics</span>
<span class="hljs-attr">revision:</span> <span class="hljs-number">2</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">|
  # Customer Lifetime Value Pipeline
  Calculates CLV from multiple data sources.
</span>
  <span class="hljs-comment">## Business Logic</span>
  <span class="hljs-number">1</span><span class="hljs-string">.</span> <span class="hljs-string">Extract</span> <span class="hljs-string">raw</span> <span class="hljs-string">data</span> <span class="hljs-string">from</span> <span class="hljs-number">3</span> <span class="hljs-string">sources</span>
  <span class="hljs-number">2</span><span class="hljs-string">.</span> <span class="hljs-string">Clean</span> <span class="hljs-string">and</span> <span class="hljs-string">standardize</span>
  <span class="hljs-number">3</span><span class="hljs-string">.</span> <span class="hljs-string">Calculate</span> <span class="hljs-string">metrics</span>
  <span class="hljs-number">4</span><span class="hljs-string">.</span> <span class="hljs-string">Load</span> <span class="hljs-string">to</span> <span class="hljs-string">data</span> <span class="hljs-string">warehouse</span>
  <span class="hljs-number">5</span><span class="hljs-string">.</span> <span class="hljs-string">Generate</span> <span class="hljs-string">reports</span>

  <span class="hljs-comment">## Technical Details</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Processes</span> <span class="hljs-string">~10M</span> <span class="hljs-string">records</span> <span class="hljs-string">daily</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Runtime target:</span> <span class="hljs-string">&lt;</span> <span class="hljs-number">2</span> <span class="hljs-string">hours</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Storage:</span> <span class="hljs-string">S3</span> <span class="hljs-string">for</span> <span class="hljs-string">raw,</span> <span class="hljs-string">Snowflake</span> <span class="hljs-string">for</span> <span class="hljs-string">processed</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">Alerting:</span> <span class="hljs-string">Slack</span> <span class="hljs-string">on</span> <span class="hljs-string">failure</span>

<span class="hljs-attr">labels:</span>
  <span class="hljs-attr">team:</span> <span class="hljs-string">marketing-analytics</span>
  <span class="hljs-attr">cost-center:</span> <span class="hljs-string">marketing</span>
  <span class="hljs-attr">data-classification:</span> <span class="hljs-string">internal</span>
  <span class="hljs-attr">sla:</span> <span class="hljs-string">business-hours</span>
  <span class="hljs-attr">environment:</span> <span class="hljs-string">production</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">retention_days:</span> <span class="hljs-number">90</span>
  <span class="hljs-attr">batch_size:</span> <span class="hljs-number">50000</span>
  <span class="hljs-attr">regions:</span> [<span class="hljs-string">"us-east"</span>, <span class="hljs-string">"eu-west"</span>, <span class="hljs-string">"ap-southeast"</span>]

<span class="hljs-attr">inputs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">processing_date</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">DATETIME</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ now() | date('yyyy-MM-dd') }}</span>"</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">force_reprocess</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">BOOLEAN</span>
    <span class="hljs-attr">defaults:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># ===== NAMESPACE: Organization =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">setup_namespace</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.namespace.UpdateNamespace</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ namespace.id }}</span>"</span>
    <span class="hljs-attr">variables:</span>
      <span class="hljs-attr">last_processed:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ inputs.processing_date }}</span>"</span>
      <span class="hljs-attr">pipeline_version:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ flow.revision }}</span>"</span>

  <span class="hljs-comment"># ===== FLOW: Control Structure =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">validate_inputs</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
    <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
      {{ inputs.processing_date | date('yyyy-MM-dd') &lt;= now() | date('yyyy-MM-dd') }}
</span>    <span class="hljs-attr">errorMessage:</span> <span class="hljs-string">"Processing date cannot be in the future"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">check_previous_run</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Previous</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ namespace.id }}</span>"</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ flow.id }}</span>"</span>
    <span class="hljs-attr">successOnly:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">should_run</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
    <span class="hljs-attr">expression:</span> <span class="hljs-string">&gt;
      {{ inputs.force_reprocess }} or
      {{ outputs.check_previous_run.executions | length == 0 }} or
      {{ outputs.check_previous_run.executions[0].state.current != 'SUCCESS' }}
</span>
  <span class="hljs-comment"># ===== TASKS: Parallel Processing =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_parallel</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Parallel</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_sales</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.http.Download</span>
        <span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.acme.com/sales/<span class="hljs-template-variable">{{ inputs.processing_date }}</span>"</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">"Extract sales data"</span>
        <span class="hljs-attr">outputFile:</span> <span class="hljs-string">"sales_raw.json"</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_customers</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.postgresql.Query</span>
        <span class="hljs-attr">sql:</span> <span class="hljs-string">&gt;
          SELECT * FROM customers 
          WHERE updated_at &gt;= '{{ inputs.processing_date }}'
</span>        <span class="hljs-attr">description:</span> <span class="hljs-string">"Extract customer data"</span>
        <span class="hljs-attr">fetch:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">extract_marketing</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.ssh.Commands</span>
        <span class="hljs-attr">host:</span> <span class="hljs-string">marketing-db.acme.com</span>
        <span class="hljs-attr">username:</span> <span class="hljs-string">etl_user</span>
        <span class="hljs-attr">privateKey:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('MARKETING_SSH_KEY') }}</span>"</span>
        <span class="hljs-attr">commands:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"mysqldump marketing events --where=\"date='<span class="hljs-template-variable">{{ inputs.processing_date }}</span>'\""</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">"Extract marketing events"</span>

    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Expression</span>
        <span class="hljs-attr">expression:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.should_run.value }}</span>"</span>

  <span class="hljs-comment"># ===== TASKS: Transformation =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">transform_data</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Transform and calculate CLV"</span>
    <span class="hljs-attr">inputFiles:</span>
      <span class="hljs-attr">sales.json:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.extract_parallel.outputs.extract_sales.uri }}</span>"</span>
      <span class="hljs-attr">customers.json:</span> <span class="hljs-string">|
        {{ outputs.extract_parallel.outputs.extract_customers.rows | tojson }}
</span>    <span class="hljs-attr">script:</span> <span class="hljs-string">|
      import pandas as pd
      import json
      from datetime import datetime
</span>
      <span class="hljs-comment"># Load data</span>
      <span class="hljs-string">sales</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.read_json('sales.json')</span>
      <span class="hljs-string">customers</span> <span class="hljs-string">=</span> <span class="hljs-string">pd.DataFrame(json.loads('customers.json'))</span>

      <span class="hljs-comment"># Complex transformation logic</span>
      <span class="hljs-comment"># ... (50 lines of pandas transformations)</span>

      <span class="hljs-comment"># Calculate CLV</span>
      <span class="hljs-string">clv_df</span> <span class="hljs-string">=</span> <span class="hljs-string">calculate_clv(sales,</span> <span class="hljs-string">customers)</span>

      <span class="hljs-comment"># Save results</span>
      <span class="hljs-string">clv_df.to_parquet('clv_calculated.parquet')</span>

      <span class="hljs-comment"># Generate summary</span>
      <span class="hljs-string">summary</span> <span class="hljs-string">=</span> {
          <span class="hljs-attr">'total_customers':</span> <span class="hljs-string">len(clv_df)</span>,
          <span class="hljs-attr">'avg_clv':</span> <span class="hljs-string">clv_df</span>[<span class="hljs-string">'clv'</span>]<span class="hljs-string">.mean()</span>,
          <span class="hljs-attr">'processing_date':</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ inputs.processing_date }}</span>'</span>,
          <span class="hljs-attr">'generated_at':</span> <span class="hljs-string">datetime.now().isoformat()</span>
      }

      <span class="hljs-string">with</span> <span class="hljs-string">open('summary.json',</span> <span class="hljs-string">'w'</span><span class="hljs-string">)</span> <span class="hljs-attr">as f:</span>
          <span class="hljs-string">json.dump(summary,</span> <span class="hljs-string">f)</span>

    <span class="hljs-attr">taskRunner:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
      <span class="hljs-attr">memory:</span> <span class="hljs-string">8Gi</span>
      <span class="hljs-attr">cpu:</span> <span class="hljs-number">4</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">python:3.11-slim</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">extract_parallel</span>

  <span class="hljs-comment"># ===== TASKS: Loading =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">load_to_warehouse</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.jdbc.snowflake.Load</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Load CLV data to Snowflake"</span>
    <span class="hljs-attr">from:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_data.outputFiles['clv_calculated.parquet'] }}</span>"</span>
    <span class="hljs-attr">table:</span> <span class="hljs-string">customer_clv</span>
    <span class="hljs-attr">schema:</span> <span class="hljs-string">marketing</span>
    <span class="hljs-attr">warehouse:</span> <span class="hljs-string">analytics_wh</span>
    <span class="hljs-attr">role:</span> <span class="hljs-string">etl_role</span>
    <span class="hljs-attr">stage:</span> <span class="hljs-string">@clv_stage</span>
    <span class="hljs-attr">fileFormat:</span> <span class="hljs-string">"(TYPE = PARQUET)"</span>

    <span class="hljs-comment"># Optimize loading</span>
    <span class="hljs-attr">copyOptions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">ON_ERROR</span> <span class="hljs-string">=</span> <span class="hljs-string">CONTINUE</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">PURGE</span> <span class="hljs-string">=</span> <span class="hljs-literal">TRUE</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">transform_data</span>

  <span class="hljs-comment"># ===== TASKS: Quality Checks =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">data_quality_check</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">acme.data.quality</span>
    <span class="hljs-attr">flowId:</span> <span class="hljs-string">validate-clv</span>
    <span class="hljs-attr">wait:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">transmit:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">data_file</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_data.outputFiles['clv_calculated.parquet'] }}</span>"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">expected_rows</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_data.vars.summary.total_customers }}</span>"</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">transform_data</span>

  <span class="hljs-comment"># ===== TASKS: Reporting =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_reports</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.EachSequential</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.regions }}</span>"</span>
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">generate_{{</span> <span class="hljs-string">task.value</span> <span class="hljs-string">}}_report</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.scripts.python.Script</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">"Generate regional CLV report"</span>
        <span class="hljs-attr">script:</span> <span class="hljs-string">|
          # Region-specific reporting logic
          generate_region_report('{{ task.value }}')
</span>        <span class="hljs-attr">taskRunner:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
          <span class="hljs-attr">memory:</span> <span class="hljs-string">2Gi</span>
          <span class="hljs-attr">cpu:</span> <span class="hljs-number">1</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">load_to_warehouse</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">data_quality_check</span>

  <span class="hljs-comment"># ===== TASKS: Cleanup =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">cleanup</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.fs.Delete</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Clean up temporary files"</span>
    <span class="hljs-attr">uri:</span> <span class="hljs-string">"kestra://<span class="hljs-template-variable">{{ execution.id }}</span>/**"</span>
    <span class="hljs-attr">regex:</span> <span class="hljs-string">".*\\.(json|parquet|tmp)$"</span>
    <span class="hljs-attr">action:</span> <span class="hljs-string">DELETE</span>

    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">generate_reports</span>

  <span class="hljs-comment"># ===== TASKS: Finalization =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">update_metadata</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.namespace.UpdateNamespace</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ namespace.id }}</span>"</span>
    <span class="hljs-attr">variables:</span>
      <span class="hljs-attr">last_successful_run:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.startDate }}</span>"</span>
      <span class="hljs-attr">records_processed:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ outputs.transform_data.vars.summary.total_customers }}</span>"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">send_success_notification</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackExecution</span>
    <span class="hljs-attr">channel:</span> <span class="hljs-string">"#marketing-alerts"</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">|
      ✅ CLV Pipeline Success
      Date: {{ inputs.processing_date }}
      Customers: {{ outputs.transform_data.vars.summary.total_customers }}
      Duration: {{ execution.duration }}
      Execution: {{ execution.id }}
</span>
    <span class="hljs-attr">dependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">cleanup</span>

  <span class="hljs-comment"># ===== ERROR HANDLING =====</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">handle_failure</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.notifications.slack.SlackExecution</span>
    <span class="hljs-attr">channel:</span> <span class="hljs-string">"#data-alerts-critical"</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">|
      🔴 CLV Pipeline Failed
      Error: {{ execution.state.current }}
      Date: {{ inputs.processing_date }}
      Execution: {{ execution.id }}
      Link: {{ serverUrl }}/ui/executions/{{ execution.id }}
</span>
    <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Execution</span>
        <span class="hljs-attr">states:</span> [<span class="hljs-string">FAILED</span>, <span class="hljs-string">KILLED</span>, <span class="hljs-string">WARNING</span>]

<span class="hljs-attr">triggers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">daily_schedule</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Schedule</span>
    <span class="hljs-attr">cron:</span> <span class="hljs-string">"0 2 * * *"</span>  <span class="hljs-comment"># 2 AM daily</span>
    <span class="hljs-attr">timezone:</span> <span class="hljs-string">UTC</span>
    <span class="hljs-attr">backfill:</span>
      <span class="hljs-attr">start:</span> <span class="hljs-number">2024-01-01T00:00:00Z</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">manual_trigger</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.trigger.Webhook</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">clv-pipeline</span>

<span class="hljs-attr">timeout:</span> <span class="hljs-string">PT4H</span>  <span class="hljs-comment"># Fail if takes longer than 4 hours</span>

<span class="hljs-comment"># Flow-level error handling</span>
<span class="hljs-attr">listeners:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">conditions:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.condition.Execution</span>
        <span class="hljs-attr">states:</span> [<span class="hljs-string">FAILED</span>]
    <span class="hljs-attr">tasks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">emergency_cleanup</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">system.emergency</span>
        <span class="hljs-attr">flowId:</span> <span class="hljs-string">cleanup-failed-execution</span>
        <span class="hljs-attr">transmit:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">execution_id</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ execution.id }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-best-practices-summary"><strong>Best Practices Summary</strong></h2>
<h3 id="heading-for-namespaces"><strong>For Namespaces:</strong></h3>
<ol>
<li><p>Use hierarchical naming: <code>company.department.function.frequency</code></p>
</li>
<li><p>Set namespace variables for configuration</p>
</li>
<li><p>Use permissions for access control</p>
</li>
<li><p>Document each namespace's purpose</p>
</li>
<li><p>Keep related flows together</p>
</li>
</ol>
<h3 id="heading-for-flows"><strong>For Flows:</strong></h3>
<ol>
<li><p>One flow = one business process</p>
</li>
<li><p>Use descriptive IDs and descriptions</p>
</li>
<li><p>Version control your flows</p>
</li>
<li><p>Use labels for filtering and management</p>
</li>
<li><p>Set appropriate timeouts</p>
</li>
</ol>
<h3 id="heading-for-tasks"><strong>For Tasks:</strong></h3>
<ol>
<li><p>Each task should do one thing well</p>
</li>
<li><p>Use descriptive task IDs</p>
</li>
<li><p>Set resource limits appropriately</p>
</li>
<li><p>Implement proper error handling</p>
</li>
<li><p>Use subflows for complex logic</p>
</li>
</ol>
<h3 id="heading-performance-tips"><strong>Performance Tips:</strong></h3>
<ol>
<li><p>Use Parallel tasks for independent operations</p>
</li>
<li><p>Cache expensive operations</p>
</li>
<li><p>Set appropriate batch sizes</p>
</li>
<li><p>Monitor task durations</p>
</li>
<li><p>Use appropriate worker groups</p>
</li>
</ol>
<h3 id="heading-maintainability"><strong>Maintainability:</strong></h3>
<ol>
<li><p>Keep flows under 20 tasks (use subflows)</p>
</li>
<li><p>Document complex logic</p>
</li>
<li><p>Use consistent naming conventions</p>
</li>
<li><p>Test flows before production</p>
</li>
<li><p>Review flow changes</p>
</li>
</ol>
<hr />
<h2 id="heading-common-pitfalls-and-how-to-avoid-them"><strong>Common Pitfalls and How to Avoid Them</strong></h2>
<h3 id="heading-pitfall-1-the-monolithic-flow"><strong>Pitfall 1: The Monolithic Flow</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD: 50+ tasks in one flow</span>
<span class="hljs-comment"># GOOD: Break into subflows</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">process_data</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.flow.Subflow</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">data.processing</span>
  <span class="hljs-attr">flowId:</span> <span class="hljs-string">transform-pipeline</span>
</code></pre>
<h3 id="heading-pitfall-2-hardcoded-values"><strong>Pitfall 2: Hardcoded Values</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD:</span>
<span class="hljs-attr">uri:</span> <span class="hljs-string">"https://api.company.com/data"</span>
<span class="hljs-comment"># GOOD:</span>
<span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ vars.api_base_url }}</span>/data"</span>
<span class="hljs-comment"># BETTER:</span>
<span class="hljs-attr">uri:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ secret('API_BASE_URL') }}</span>/data"</span>
</code></pre>
<h3 id="heading-pitfall-3-no-error-handling"><strong>Pitfall 3: No Error Handling</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD: Task without retry</span>
<span class="hljs-comment"># GOOD: Add retry configuration</span>
<span class="hljs-attr">retry:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
  <span class="hljs-attr">maxAttempt:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">delay:</span> <span class="hljs-string">PT10S</span>
</code></pre>
<h3 id="heading-pitfall-4-resource-hogging"><strong>Pitfall 4: Resource Hogging</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># BAD: No resource limits</span>
<span class="hljs-comment"># GOOD: Set appropriate limits</span>
<span class="hljs-attr">taskRunner:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">io.kestra.plugin.core.runner.Process</span>
  <span class="hljs-attr">memory:</span> <span class="hljs-string">1Gi</span>
  <span class="hljs-attr">cpu:</span> <span class="hljs-string">500m</span>
</code></pre>
<hr />
<h2 id="heading-next-steps-hands-on-exercise"><strong>Next Steps: Hands-On Exercise</strong></h2>
<p>To solidify your understanding, try this exercise:</p>
<ol>
<li><p><strong>Create a namespace structure</strong> for your organization</p>
</li>
<li><p><strong>Build a modular flow</strong> that:</p>
<ul>
<li><p>Extracts data from a public API</p>
</li>
<li><p>Processes it in parallel</p>
</li>
<li><p>Handles errors gracefully</p>
</li>
<li><p>Sends notifications</p>
</li>
</ul>
</li>
<li><p><strong>Optimize the flow</strong> for performance</p>
</li>
<li><p><strong>Document everything</strong> using flow descriptions and labels</p>
</li>
</ol>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>You now understand the three pillars of Kestra architecture:</p>
<ol>
<li><p><strong>Namespaces</strong> for organization and security</p>
</li>
<li><p><strong>Flows</strong> as declarative workflow blueprints</p>
</li>
<li><p><strong>Tasks</strong> as the building blocks of execution</p>
</li>
</ol>
<p>Remember: Great orchestration comes from thoughtful design, not complex code. Kestra gives you the tools—how you use them determines your success.</p>
<p><strong>Key mindset shifts:</strong></p>
<ul>
<li><p>Think in <strong>declarative</strong> terms, not imperative</p>
</li>
<li><p>Design for <strong>observability</strong> from day one</p>
</li>
<li><p>Build <strong>modular</strong> components</p>
</li>
<li><p>Plan for <strong>failure</strong> as a normal case</p>
</li>
<li><p>Document <strong>everything</strong></p>
</li>
</ul>
<p>In the next article, we'll dive into <strong>Building Your First ETL Pipeline</strong>, where we'll apply these concepts to solve real-world data problems.</p>
<p><strong>Before the next article, try:</strong></p>
<ol>
<li><p>Refactor one of your existing workflows using namespace variables</p>
</li>
<li><p>Create a task template for common operations</p>
</li>
<li><p>Set up proper error handling in a critical flow</p>
</li>
<li><p>Document a flow using the complete metadata format</p>
</li>
</ol>
<p>Happy orchestrating! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Article 2: Installing Kestra: Your First Steps to Powerful Orchestration.]]></title><description><![CDATA[Complete Setup Guide for Local, Docker, and Cloud Deployments.
Introduction: Why Installation Matters
Think of Kestra as the conductor of your data orchestra. Just as a great conductor needs the right stage setup, Kestra needs proper installation to ...]]></description><link>https://techwasti.com/article-2-installing-kestra-your-first-steps-to-powerful-orchestration</link><guid isPermaLink="true">https://techwasti.com/article-2-installing-kestra-your-first-steps-to-powerful-orchestration</guid><category><![CDATA[#Kestra]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[data engineer]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Tue, 03 Feb 2026 04:23:51 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-complete-setup-guide-for-local-docker-and-cloud-deployments"><strong>Complete Setup Guide for Local, Docker, and Cloud Deployments.</strong></h2>
<h3 id="heading-introduction-why-installation-matters"><strong>Introduction: Why Installation Matters</strong></h3>
<p>Think of Kestra as the conductor of your data orchestra. Just as a great conductor needs the right stage setup, Kestra needs proper installation to perform at its best. In this guide, we'll walk through every installation option—from a quick local setup to enterprise-grade deployments.</p>
<p><strong>Quick Decision Guide:</strong></p>
<ul>
<li><p><strong>Just exploring?</strong> → Use Docker standalone (5 minutes)</p>
</li>
<li><p><strong>Developing workflows?</strong> → Use Docker Compose (10 minutes)</p>
</li>
<li><p><strong>Production deployment?</strong> → Use Kubernetes/Helm (30 minutes)</p>
</li>
<li><p><strong>Enterprise scale?</strong> → Use Terraform on cloud (60 minutes)</p>
</li>
</ul>
<hr />
<h2 id="heading-option-1-local-development-fastest-path"><strong>Option 1: Local Development (Fastest Path)</strong></h2>
<h3 id="heading-prerequisites-checklist"><strong>Prerequisites Checklist</strong></h3>
<p>Before we begin, ensure you have:</p>
<ul>
<li><p>✅ Docker installed (<a target="_blank" href="https://docs.docker.com/get-docker/">Get Docker</a>)</p>
</li>
<li><p>✅ 2GB free RAM</p>
</li>
<li><p>✅ 10GB free disk space</p>
</li>
<li><p>✅ Internet connection</p>
</li>
</ul>
<h3 id="heading-method-a-quick-start-with-docker-5-minutes"><strong>Method A: Quick Start with Docker (5 Minutes)</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Run this single command</span>
docker run --rm \
  -p 8080:8080 \
  -v $(<span class="hljs-built_in">pwd</span>)/flows:/app/flows \
  -v $(<span class="hljs-built_in">pwd</span>)/storage:/app/storage \
  kestra/kestra:latest \
  standalone

<span class="hljs-comment"># Or for Windows PowerShell</span>
docker run --rm `
  -p 8080:8080 `
  -v <span class="hljs-variable">${PWD}</span>/flows:/app/flows `
  -v <span class="hljs-variable">${PWD}</span>/storage:/app/storage `
  kestra/kestra:latest `
  standalone
</code></pre>
<p><strong>What this does:</strong></p>
<ul>
<li><p>Starts Kestra with embedded databases</p>
</li>
<li><p>Maps local directories for flows and storage</p>
</li>
<li><p>Opens port 8080 for the web UI</p>
</li>
<li><p>Uses temporary containers (removed on stop)</p>
</li>
</ul>
<p><strong>Access the UI:</strong></p>
<ol>
<li><p>Open <a target="_blank" href="http://localhost:8080/">http://localhost:8080</a></p>
</li>
<li><p>Default credentials: <code>admin</code> / <code>password</code> (change immediately!)</p>
</li>
</ol>
<h3 id="heading-method-b-docker-compose-for-development-recommended"><strong>Method B: Docker Compose for Development (Recommended)</strong></h3>
<p>Create a <code>docker-compose.yml</code> file:</p>
<pre><code class="lang-bash">version: <span class="hljs-string">'3.8'</span>

services:
  kestra:
    image: kestra/kestra:latest
    container_name: kestra-server
    ports:
      - <span class="hljs-string">"8080:8080"</span>
      - <span class="hljs-string">"8081:8081"</span>  <span class="hljs-comment"># For metrics if needed</span>
    environment:
      - KESTRA_CONFIGURATION= |
          kestra:
            server:
              basic-auth:
                enabled: <span class="hljs-literal">true</span>
                username: admin
                password: <span class="hljs-variable">${KESTRA_PASSWORD:-changeme}</span>
            repository:
              <span class="hljs-built_in">type</span>: postgres
            storage:
              <span class="hljs-built_in">type</span>: <span class="hljs-built_in">local</span>
            queue:
              <span class="hljs-built_in">type</span>: postgres
    volumes:
      - ./flows:/app/flows      <span class="hljs-comment"># Your workflow definitions</span>
      - ./plugins:/app/plugins  <span class="hljs-comment"># Custom plugins</span>
      - ./logs:/app/logs        <span class="hljs-comment"># Execution logs</span>
      - ./storage:/app/storage  <span class="hljs-comment"># File storage</span>
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:15-alpine
    container_name: kestra-postgres
    environment:
      POSTGRES_DB: kestra
      POSTGRES_USER: kestra
      POSTGRES_PASSWORD: kestra
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init-db:/docker-entrypoint-initdb.d  <span class="hljs-comment"># Optional: initial scripts</span>
    ports:
      - <span class="hljs-string">"5432:5432"</span>  <span class="hljs-comment"># Optional: expose for external tools</span>
    healthcheck:
      <span class="hljs-built_in">test</span>: [<span class="hljs-string">"CMD-SHELL"</span>, <span class="hljs-string">"pg_isready -U kestra"</span>]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres_data:
</code></pre>
<p><strong>Start everything with one command:</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create necessary directories</span>
mkdir -p flows plugins logs storage init-db

<span class="hljs-comment"># Start all services</span>
docker-compose up -d

<span class="hljs-comment"># Check status</span>
docker-compose ps

<span class="hljs-comment"># View logs</span>
docker-compose logs -f kestra
</code></pre>
<p><strong>What you get:</strong></p>
<ul>
<li><p>✅ Persistent database (PostgreSQL)</p>
</li>
<li><p>✅ File storage that survives restarts</p>
</li>
<li><p>✅ Proper service health checks</p>
</li>
<li><p>✅ Easy log viewing</p>
</li>
<li><p>✅ Production-like setup locally</p>
</li>
</ul>
<hr />
<h2 id="heading-option-2-production-ready-kubernetes-setup"><strong>Option 2: Production-Ready Kubernetes Setup</strong></h2>
<h3 id="heading-prerequisites"><strong>Prerequisites:</strong></h3>
<ul>
<li><p>Kubernetes cluster (Minikube, Docker Desktop, EKS, AKS, GKE)</p>
</li>
<li><p>Helm 3+</p>
</li>
<li><p>kubectl configured</p>
</li>
</ul>
<h3 id="heading-step-1-create-namespace-and-secrets"><strong>Step 1: Create Namespace and Secrets</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Create namespace</span>
kubectl create namespace kestra

<span class="hljs-comment"># Create a secret for credentials</span>
kubectl create secret generic kestra-secrets \
  --namespace kestra \
  --from-literal=postgres-password=<span class="hljs-string">'YourSecurePassword123!'</span> \
  --from-literal=admin-password=<span class="hljs-string">'AnotherSecurePassword456!'</span>

<span class="hljs-comment"># Create storage class (if needed)</span>
cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: kestra-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
EOF
</code></pre>
<h3 id="heading-step-2-deploy-with-helm"><strong>Step 2: Deploy with Helm</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Add Kestra Helm repository</span>
helm repo add kestra https://kestra.io/helm
helm repo update

<span class="hljs-comment"># Install Kestra with custom values</span>
cat &gt; kestra-values.yaml &lt;&lt; EOF
<span class="hljs-comment"># kestra-values.yaml</span>
global:
  storageClass: kestra-storage

server:
  replicaCount: 2
  autoscaling:
    enabled: <span class="hljs-literal">true</span>
    minReplicas: 2
    maxReplicas: 10
  resources:
    requests:
      memory: <span class="hljs-string">"1Gi"</span>
      cpu: <span class="hljs-string">"500m"</span>
    limits:
      memory: <span class="hljs-string">"2Gi"</span>
      cpu: <span class="hljs-string">"1000m"</span>
  extraEnv:
    - name: JAVA_OPTS
      value: <span class="hljs-string">"-Xmx1g -Xms512m"</span>

postgresql:
  enabled: <span class="hljs-literal">true</span>
  auth:
    database: kestra
    username: kestra
    existingSecret: kestra-secrets
    secretKeys:
      adminPasswordKey: postgres-password
  persistence:
    size: 50Gi
    storageClass: kestra-storage

ingress:
  enabled: <span class="hljs-literal">true</span>
  className: nginx
  hosts:
    - host: kestra.yourcompany.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: kestra-tls
      hosts:
        - kestra.yourcompany.com

redis:
  enabled: <span class="hljs-literal">true</span>
  architecture: standalone
  auth:
    existingSecret: kestra-secrets
    secretKeys:
      redis-passwordKey: redis-password

elasticsearch:
  enabled: <span class="hljs-literal">true</span>  <span class="hljs-comment"># For enhanced logging and search</span>
EOF

<span class="hljs-comment"># Install the chart</span>
helm install kestra kestra/kestra \
  --namespace kestra \
  --values kestra-values.yaml \
  --<span class="hljs-built_in">wait</span>

<span class="hljs-comment"># Check deployment status</span>
kubectl get all -n kestra
</code></pre>
<h3 id="heading-step-3-configure-ingress-and-tls"><strong>Step 3: Configure Ingress and TLS</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># If using cert-manager for TLS</span>
cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: kestra-tls
  namespace: kestra
spec:
  secretName: kestra-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - kestra.yourcompany.com
EOF
</code></pre>
<h3 id="heading-step-4-verify-deployment"><strong>Step 4: Verify Deployment</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Check pods</span>
kubectl get pods -n kestra -w

<span class="hljs-comment"># Check services</span>
kubectl get svc -n kestra

<span class="hljs-comment"># View logs</span>
kubectl logs -n kestra deployment/kestra-server -f

<span class="hljs-comment"># Port-forward for local access (if needed)</span>
kubectl port-forward -n kestra svc/kestra-server 8080:80
</code></pre>
<hr />
<h2 id="heading-option-3-cloud-specific-deployments"><strong>Option 3: Cloud-Specific Deployments</strong></h2>
<h3 id="heading-aws-ecs-setup-fargate"><strong>AWS ECS Setup (Fargate)</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># ecs-task-definition.json</span>
{
  <span class="hljs-string">"family"</span>: <span class="hljs-string">"kestra-server"</span>,
  <span class="hljs-string">"networkMode"</span>: <span class="hljs-string">"awsvpc"</span>,
  <span class="hljs-string">"taskRoleArn"</span>: <span class="hljs-string">"arn:aws:iam::123456789012:role/ecsTaskRole"</span>,
  <span class="hljs-string">"executionRoleArn"</span>: <span class="hljs-string">"arn:aws:iam::123456789012:role/ecsTaskExecutionRole"</span>,
  <span class="hljs-string">"cpu"</span>: <span class="hljs-string">"2048"</span>,
  <span class="hljs-string">"memory"</span>: <span class="hljs-string">"4096"</span>,
  <span class="hljs-string">"containerDefinitions"</span>: [
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"kestra"</span>,
      <span class="hljs-string">"image"</span>: <span class="hljs-string">"kestra/kestra:latest"</span>,
      <span class="hljs-string">"portMappings"</span>: [
        {
          <span class="hljs-string">"containerPort"</span>: 8080,
          <span class="hljs-string">"protocol"</span>: <span class="hljs-string">"tcp"</span>
        }
      ],
      <span class="hljs-string">"environment"</span>: [
        {
          <span class="hljs-string">"name"</span>: <span class="hljs-string">"KESTRA_CONFIGURATION"</span>,
          <span class="hljs-string">"value"</span>: <span class="hljs-string">"kestra.repository.type=postgres\nkestra.storage.type=s3"</span>
        }
      ],
      <span class="hljs-string">"secrets"</span>: [
        {
          <span class="hljs-string">"name"</span>: <span class="hljs-string">"DB_PASSWORD"</span>,
          <span class="hljs-string">"valueFrom"</span>: <span class="hljs-string">"arn:aws:secretsmanager:region:account:secret:db_password"</span>
        }
      ],
      <span class="hljs-string">"logConfiguration"</span>: {
        <span class="hljs-string">"logDriver"</span>: <span class="hljs-string">"awslogs"</span>,
        <span class="hljs-string">"options"</span>: {
          <span class="hljs-string">"awslogs-group"</span>: <span class="hljs-string">"/ecs/kestra"</span>,
          <span class="hljs-string">"awslogs-region"</span>: <span class="hljs-string">"us-east-1"</span>,
          <span class="hljs-string">"awslogs-stream-prefix"</span>: <span class="hljs-string">"ecs"</span>
        }
      }
    }
  ]
}
</code></pre>
<p><strong>Deploy with AWS CLI:</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Register task definition</span>
aws ecs register-task-definition \
  --cli-input-json file://ecs-task-definition.json

<span class="hljs-comment"># Create service</span>
aws ecs create-service \
  --cluster production \
  --service-name kestra \
  --task-definition kestra-server:1 \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration <span class="hljs-string">"awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-zzz],assignPublicIp=ENABLED}"</span>
</code></pre>
<h3 id="heading-google-cloud-run-serverless"><strong>Google Cloud Run (Serverless)</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Deploy to Cloud Run</span>
gcloud run deploy kestra \
  --image=kestra/kestra:latest \
  --platform=managed \
  --region=us-central1 \
  --allow-unauthenticated \
  --memory=2Gi \
  --cpu=2 \
  --set-env-vars=<span class="hljs-string">"KESTRA_CONFIGURATION=..."</span>

<span class="hljs-comment"># Set up Cloud SQL connection</span>
gcloud run services update kestra \
  --add-cloudsql-instances=PROJECT:REGION:INSTANCE \
  --set-env-vars=<span class="hljs-string">"DB_HOST=/cloudsql/PROJECT:REGION:INSTANCE"</span>
</code></pre>
<h3 id="heading-azure-container-instances"><strong>Azure Container Instances</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Create container instance</span>
az container create \
  --resource-group kestra-rg \
  --name kestra-server \
  --image kestra/kestra:latest \
  --ports 8080 \
  --memory 4 \
  --cpu 2 \
  --environment-variables \
    KESTRA_CONFIGURATION=<span class="hljs-string">'kestra.repository.type=postgres'</span> \
  --dns-name-label kestra-unique \
  --azure-file-volume-share-name kestra-data \
  --azure-file-volume-account-name mystorageaccount \
  --azure-file-volume-account-key <span class="hljs-variable">$STORAGE_KEY</span>
</code></pre>
<hr />
<h2 id="heading-option-4-bare-metal-vm-installation"><strong>Option 4: Bare Metal / VM Installation</strong></h2>
<p>For environments where containers aren't an option:</p>
<h3 id="heading-step-1-install-java-and-dependencies"><strong>Step 1: Install Java and Dependencies</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Ubuntu/Debian</span>
sudo apt update
sudo apt install -y openjdk-17-jre-headless postgresql-15 redis-server

<span class="hljs-comment"># CentOS/RHEL</span>
sudo yum install -y java-17-openjdk postgresql15-server redis

<span class="hljs-comment"># macOS</span>
brew install openjdk@17 postgresql@15 redis
</code></pre>
<h3 id="heading-step-2-configure-database"><strong>Step 2: Configure Database</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize PostgreSQL</span>
sudo postgresql-setup initdb
sudo systemctl start postgresql
sudo systemctl <span class="hljs-built_in">enable</span> postgresql

<span class="hljs-comment"># Create Kestra database and user</span>
sudo -u postgres psql &lt;&lt; EOF
CREATE DATABASE kestra;
CREATE USER kestra WITH PASSWORD <span class="hljs-string">'secure_password'</span>;
GRANT ALL PRIVILEGES ON DATABASE kestra TO kestra;
ALTER DATABASE kestra SET timezone TO <span class="hljs-string">'UTC'</span>;
EOF

<span class="hljs-comment"># Update pg_hba.conf for password auth</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"host    kestra          kestra          127.0.0.1/32            md5"</span> | sudo tee -a /etc/postgresql/15/main/pg_hba.conf
sudo systemctl restart postgresql
</code></pre>
<h3 id="heading-step-3-install-kestra"><strong>Step 3: Install Kestra</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Download Kestra</span>
wget https://github.com/kestra-io/kestra/releases/latest/download/kestra-standalone.jar -O /opt/kestra/kestra.jar

<span class="hljs-comment"># Create systemd service</span>
cat &lt;&lt; EOF | sudo tee /etc/systemd/system/kestra.service
[Unit]
Description=Kestra Workflow Orchestrator
After=network.target postgresql.service redis.service

[Service]
Type=simple
User=kestra
Group=kestra
WorkingDirectory=/opt/kestra
ExecStart=/usr/bin/java -Xmx2g -jar /opt/kestra/kestra.jar server standalone

<span class="hljs-comment"># Security hardening</span>
NoNewPrivileges=<span class="hljs-literal">true</span>
PrivateTmp=<span class="hljs-literal">true</span>
ProtectSystem=strict
ReadWritePaths=/var/lib/kestra

[Install]
WantedBy=multi-user.target
EOF

<span class="hljs-comment"># Create user and directories</span>
sudo useradd -r -s /bin/<span class="hljs-literal">false</span> kestra
sudo mkdir -p /opt/kestra /var/lib/kestra/{flows,storage,logs,plugins}
sudo chown -R kestra:kestra /opt/kestra /var/lib/kestra

<span class="hljs-comment"># Start service</span>
sudo systemctl daemon-reload
sudo systemctl <span class="hljs-built_in">enable</span> kestra
sudo systemctl start kestra
sudo systemctl status kestra
</code></pre>
<hr />
<h2 id="heading-verification-and-testing"><strong>Verification and Testing</strong></h2>
<h3 id="heading-health-check"><strong>Health Check</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Using curl</span>
curl http://localhost:8080/api/v1/health

<span class="hljs-comment"># Expected response</span>
{
  <span class="hljs-string">"status"</span>: <span class="hljs-string">"UP"</span>,
  <span class="hljs-string">"components"</span>: {
    <span class="hljs-string">"database"</span>: {
      <span class="hljs-string">"status"</span>: <span class="hljs-string">"UP"</span>
    },
    <span class="hljs-string">"storage"</span>: {
      <span class="hljs-string">"status"</span>: <span class="hljs-string">"UP"</span>
    }
  }
}

<span class="hljs-comment"># Using API</span>
curl -X GET <span class="hljs-string">"http://localhost:8080/api/v1/configs"</span> \
  -H <span class="hljs-string">"Authorization: Basic <span class="hljs-subst">$(echo -n 'admin:password' | base64)</span>"</span>
</code></pre>
<h3 id="heading-create-your-first-flow-programmatically"><strong>Create Your First Flow Programmatically</strong></h3>
<pre><code class="lang-python"><span class="hljs-comment"># test_kestra.py</span>
<span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> yaml

<span class="hljs-comment"># Configure</span>
BASE_URL = <span class="hljs-string">"http://localhost:8080"</span>
USERNAME = <span class="hljs-string">"admin"</span>
PASSWORD = <span class="hljs-string">"password"</span>
NAMESPACE = <span class="hljs-string">"demo"</span>

<span class="hljs-comment"># Create namespace</span>
auth = (USERNAME, PASSWORD)
headers = {<span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>}

namespace_payload = {
    <span class="hljs-string">"namespace"</span>: NAMESPACE,
    <span class="hljs-string">"description"</span>: <span class="hljs-string">"Demo namespace"</span>
}

response = requests.post(
    <span class="hljs-string">f"<span class="hljs-subst">{BASE_URL}</span>/api/v1/namespaces"</span>,
    json=namespace_payload,
    auth=auth,
    headers=headers
)

<span class="hljs-comment"># Create a simple flow</span>
flow_yaml = <span class="hljs-string">"""
id: hello-world
namespace: demo
description: My first Kestra flow

tasks:
  - id: hello
    type: io.kestra.plugin.core.log.Log
    message: "Hello, Kestra! 🎉"

  - id: get-time
    type: io.kestra.plugin.core.debug.Return
    format: "Flow started at {{ execution.startDate }}"

triggers:
  - id: schedule
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "*/5 * * * *"  # Every 5 minutes
"""</span>

<span class="hljs-comment"># Create flow</span>
response = requests.post(
    <span class="hljs-string">f"<span class="hljs-subst">{BASE_URL}</span>/api/v1/flows/<span class="hljs-subst">{NAMESPACE}</span>"</span>,
    data=flow_yaml,
    auth=auth,
    headers={<span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/yaml"</span>}
)

<span class="hljs-comment"># Execute flow</span>
response = requests.post(
    <span class="hljs-string">f"<span class="hljs-subst">{BASE_URL}</span>/api/v1/executions/trigger/<span class="hljs-subst">{NAMESPACE}</span>/hello-world"</span>,
    auth=auth,
    headers=headers
)

print(<span class="hljs-string">"Flow created and triggered successfully!"</span>)
</code></pre>
<h3 id="heading-test-with-kestra-cli"><strong>Test with Kestra CLI</strong></h3>
<p>bash</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install CLI</span>
pip install kestra

<span class="hljs-comment"># Configure</span>
kestra config set-endpoint http://localhost:8080
kestra config set-username admin
kestra config set-password password

<span class="hljs-comment"># Test commands</span>
kestra namespace list
kestra flow list --namespace demo
kestra flow execute --namespace demo --flow hello-world
kestra execution list --namespace demo
</code></pre>
<hr />
<h2 id="heading-configuration-deep-dive"><strong>Configuration Deep Dive</strong></h2>
<h3 id="heading-configuration-file-structure"><strong>Configuration File Structure</strong></h3>
<p>Create <code>kestra.yml</code> for custom configurations:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># kestra.yml</span>
<span class="hljs-attr">kestra:</span>
  <span class="hljs-attr">server:</span>
    <span class="hljs-attr">basic-auth:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">username:</span> <span class="hljs-string">admin</span>
      <span class="hljs-attr">password:</span> <span class="hljs-string">${KESTRA_PASSWORD:?Password</span> <span class="hljs-string">required}</span>

    <span class="hljs-attr">cors:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">allowed-origins:</span> [<span class="hljs-string">"https://*.yourcompany.com"</span>]

    <span class="hljs-attr">metrics:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">8081</span>
      <span class="hljs-attr">path:</span> <span class="hljs-string">/metrics</span>

  <span class="hljs-attr">repository:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">postgres:</span>
      <span class="hljs-attr">host:</span> <span class="hljs-string">${DB_HOST:localhost}</span>
      <span class="hljs-attr">port:</span> <span class="hljs-string">${DB_PORT:5432}</span>
      <span class="hljs-attr">database:</span> <span class="hljs-string">kestra</span>
      <span class="hljs-attr">username:</span> <span class="hljs-string">kestra</span>
      <span class="hljs-attr">password:</span> <span class="hljs-string">${DB_PASSWORD:?DB</span> <span class="hljs-string">password</span> <span class="hljs-string">required}</span>
      <span class="hljs-attr">pool-size:</span> <span class="hljs-number">10</span>

  <span class="hljs-attr">storage:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">s3</span>  <span class="hljs-comment"># or local, gcs, azure</span>
    <span class="hljs-attr">s3:</span>
      <span class="hljs-attr">region:</span> <span class="hljs-string">${AWS_REGION:us-east-1}</span>
      <span class="hljs-attr">bucket:</span> <span class="hljs-string">kestra-storage</span>
      <span class="hljs-attr">endpoint:</span> <span class="hljs-string">${AWS_ENDPOINT:}</span>  <span class="hljs-comment"># For MinIO or other S3-compatible</span>
      <span class="hljs-attr">access-key-id:</span> <span class="hljs-string">${AWS_ACCESS_KEY_ID:}</span>
      <span class="hljs-attr">secret-access-key:</span> <span class="hljs-string">${AWS_SECRET_ACCESS_KEY:}</span>

  <span class="hljs-attr">queue:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">postgres</span>  <span class="hljs-comment"># or redis, kafka</span>

  <span class="hljs-attr">cache:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">redis</span>
    <span class="hljs-attr">redis:</span>
      <span class="hljs-attr">host:</span> <span class="hljs-string">${REDIS_HOST:localhost}</span>
      <span class="hljs-attr">port:</span> <span class="hljs-string">${REDIS_PORT:6379}</span>
      <span class="hljs-attr">password:</span> <span class="hljs-string">${REDIS_PASSWORD:}</span>

  <span class="hljs-attr">plugins:</span>
    <span class="hljs-attr">location:</span> <span class="hljs-string">/app/plugins</span>
    <span class="hljs-attr">repositories:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">kestra-io</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">https://repo.kestra.io/repository/maven-public/</span>

  <span class="hljs-attr">tasks:</span>
    <span class="hljs-attr">default-retry:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">exponential</span>
      <span class="hljs-attr">max-attempt:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">delay:</span> <span class="hljs-string">PT5S</span>

  <span class="hljs-attr">security:</span>
    <span class="hljs-attr">secret-key:</span> <span class="hljs-string">${SECRET_KEY:?Secret</span> <span class="hljs-string">key</span> <span class="hljs-string">required</span> <span class="hljs-string">for</span> <span class="hljs-string">encryption}</span>
    <span class="hljs-attr">encryption:</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
</code></pre>
<h3 id="heading-environment-variables"><strong>Environment Variables</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># .env file example</span>
KESTRA_PASSWORD=SuperSecurePassword123!
DB_HOST=postgres
DB_PORT=5432
DB_PASSWORD=db_password_456
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
REDIS_HOST=redis
REDIS_PASSWORD=redis_pass_789
SECRET_KEY=$(openssl rand -hex 32)
</code></pre>
<h3 id="heading-run-with-custom-config"><strong>Run with Custom Config</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Docker with custom config</span>
docker run --rm \
  -p 8080:8080 \
  -v $(<span class="hljs-built_in">pwd</span>)/kestra.yml:/app/kestra.yml \
  -v $(<span class="hljs-built_in">pwd</span>)/.env:/app/.env \
  --env-file .env \
  kestra/kestra:latest \
  server standalone

<span class="hljs-comment"># Or with environment variables</span>
docker run --rm \
  -p 8080:8080 \
  -e KESTRA_PASSWORD=password \
  -e DB_HOST=postgres \
  kestra/kestra:latest \
  server standalone
</code></pre>
<hr />
<h2 id="heading-troubleshooting-guide"><strong>Troubleshooting Guide</strong></h2>
<h3 id="heading-common-issues-and-solutions"><strong>Common Issues and Solutions</strong></h3>
<h4 id="heading-1-port-already-in-use"><strong>1. Port Already in Use</strong></h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Find what's using port 8080</span>
sudo lsof -i :8080

<span class="hljs-comment"># Kill the process</span>
sudo <span class="hljs-built_in">kill</span> -9 &lt;PID&gt;

<span class="hljs-comment"># Or change Kestra port</span>
docker run --rm -p 9090:8080 kestra/kestra:latest
</code></pre>
<h4 id="heading-2-database-connection-issues"><strong>2. Database Connection Issues</strong></h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Test database connection</span>
docker <span class="hljs-built_in">exec</span> -it kestra-postgres psql -U kestra -d kestra

<span class="hljs-comment"># Reset database (development only)</span>
docker-compose down -v
docker-compose up -d
</code></pre>
<h4 id="heading-3-permission-denied-for-storage"><strong>3. Permission Denied for Storage</strong></h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Fix volume permissions</span>
sudo chown -R 1000:1000 ./flows ./storage ./logs

<span class="hljs-comment"># Or run as current user</span>
docker run --rm \
  -u $(id -u):$(id -g) \
  -v $(<span class="hljs-built_in">pwd</span>)/flows:/app/flows \
  kestra/kestra:latest
</code></pre>
<h4 id="heading-4-out-of-memory-errors"><strong>4. Out of Memory Errors</strong></h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Increase memory in docker-compose</span>
services:
  kestra:
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 1G
</code></pre>
<h4 id="heading-5-flow-not-appearing"><strong>5. Flow Not Appearing</strong></h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Check if flows are being loaded</span>
curl http://localhost:8080/api/v1/flows

<span class="hljs-comment"># Check logs for errors</span>
docker-compose logs kestra | grep -i flow

<span class="hljs-comment"># Verify directory mapping</span>
docker <span class="hljs-built_in">exec</span> -it kestra-server ls -la /app/flows
</code></pre>
<h3 id="heading-debug-commands"><strong>Debug Commands</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Get detailed system info</span>
curl http://localhost:8080/api/v1/configs/debug

<span class="hljs-comment"># Check plugin availability</span>
curl http://localhost:8080/api/v1/plugins

<span class="hljs-comment"># View server metrics</span>
curl http://localhost:8080/api/v1/metrics

<span class="hljs-comment"># Check queue status</span>
curl http://localhost:8080/api/v1/queues
</code></pre>
<hr />
<h2 id="heading-post-installation-checklist"><strong>Post-Installation Checklist</strong></h2>
<h3 id="heading-security-hardening"><strong>✅ Security Hardening</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># 1. Change default password</span>
curl -X PUT <span class="hljs-string">"http://localhost:8080/api/v1/users/admin"</span> \
  -H <span class="hljs-string">"Authorization: Basic <span class="hljs-subst">$(echo -n 'admin:password' | base64)</span>"</span> \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{"password": "NewSecurePassword123!"}'</span>

<span class="hljs-comment"># 2. Enable HTTPS</span>
<span class="hljs-comment"># 3. Set up firewall rules</span>
<span class="hljs-comment"># 4. Configure audit logging</span>
</code></pre>
<h3 id="heading-backup-strategy"><strong>✅ Backup Strategy</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Backup flows</span>
cp -r ./flows ./backups/flows-$(date +%Y%m%d)

<span class="hljs-comment"># Backup database</span>
docker <span class="hljs-built_in">exec</span> kestra-postgres pg_dump -U kestra kestra &gt; backup-$(date +%Y%m%d).sql

<span class="hljs-comment"># Backup configuration</span>
cp kestra.yml kestra.yml.backup-$(date +%Y%m%d)
</code></pre>
<h3 id="heading-monitoring-setup"><strong>✅ Monitoring Setup</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Install monitoring stack</span>
docker-compose -f monitoring/docker-compose.yml up -d

<span class="hljs-comment"># Configure Prometheus</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"
  - job_name: 'kestra'
    static_configs:
      - targets: ['kestra:8081']
"</span> &gt;&gt; prometheus/prometheus.yml
</code></pre>
<hr />
<h2 id="heading-production-deployment-checklist"><strong>Production Deployment Checklist</strong></h2>
<h3 id="heading-before-going-live"><strong>Before Going Live:</strong></h3>
<ul>
<li><p><strong>Performance Testing</strong>: Load test with 100+ concurrent flows</p>
</li>
<li><p><strong>Disaster Recovery</strong>: Test backup/restore procedures</p>
</li>
<li><p><strong>High Availability</strong>: Ensure multiple replicas are running</p>
</li>
<li><p><strong>Monitoring</strong>: Set up alerts for critical metrics</p>
</li>
<li><p><strong>Security Audit</strong>: Review access controls and encryption</p>
</li>
<li><p><strong>Documentation</strong>: Document deployment procedures</p>
</li>
<li><p><strong>Rollback Plan</strong>: Test downgrade procedure</p>
</li>
<li><p><strong>Load Balancer</strong>: Configure proper load balancing</p>
</li>
<li><p><strong>DNS Setup</strong>: Configure proper DNS records</p>
</li>
<li><p><strong>SSL/TLS</strong>: Set up proper certificates</p>
</li>
</ul>
<h3 id="heading-critical-metrics-to-monitor"><strong>Critical Metrics to Monitor:</strong></h3>
<ul>
<li><p>Flow execution success rate (&gt;99%)</p>
</li>
<li><p>Average execution time</p>
</li>
<li><p>Queue backlog size</p>
</li>
<li><p>Database connection pool usage</p>
</li>
<li><p>Memory and CPU utilization</p>
</li>
<li><p>Storage usage</p>
</li>
<li><p>API response times</p>
</li>
</ul>
<hr />
<h2 id="heading-next-steps-what-to-build-first"><strong>Next Steps: What to Build First</strong></h2>
<p>Now that Kestra is installed, here are some practical projects to start with:</p>
<h3 id="heading-project-1-data-pipeline-template"><strong>Project 1: Data Pipeline Template</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Create project structure</span>
mkdir -p flows/{extract,transform,load}
mkdir -p scripts/{python,sql}
mkdir -p config

<span class="hljs-comment"># Create your first real flow</span>
cat &gt; flows/extract/daily-sales.yml &lt;&lt; <span class="hljs-string">'EOF'</span>
id: daily-sales-extract
namespace: sales.pipeline
description: Extract daily sales data

tasks:
  - id: extract-api
    <span class="hljs-built_in">type</span>: io.kestra.plugin.core.http.Download
    uri: <span class="hljs-string">"https://api.company.com/sales/{{ execution.startDate | date('yyyy-MM-dd') }}"</span>
    outputFile: <span class="hljs-string">"sales_{{ execution.startDate | date('yyyy-MM-dd') }}.json"</span>
EOF
</code></pre>
<h3 id="heading-project-2-monitoring-dashboard"><strong>Project 2: Monitoring Dashboard</strong></h3>
<ol>
<li><p>Deploy Grafana alongside Kestra</p>
</li>
<li><p>Import Kestra dashboard templates</p>
</li>
<li><p>Set up alerts for failed executions</p>
</li>
<li><p>Create custom metrics dashboards</p>
</li>
</ol>
<h3 id="heading-project-3-cicd-pipeline"><strong>Project 3: CI/CD Pipeline</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># .github/workflows/kestra-deploy.yml</span>
name: Deploy Kestra Flows
on:
  push:
    paths:
      - <span class="hljs-string">'flows/**'</span>

<span class="hljs-built_in">jobs</span>:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Validate flows
        run: |
          docker run --rm \
            -v $(<span class="hljs-built_in">pwd</span>)/flows:/flows \
            kestra/kestra:latest \
            flow validate /flows

      - name: Deploy to Kestra
        run: |
          curl -X POST <span class="hljs-string">"https://kestra.company.com/api/v1/flows/batch"</span> \
            -H <span class="hljs-string">"Authorization: Bearer <span class="hljs-variable">${{ secrets.KESTRA_TOKEN }</span>}"</span> \
            -F <span class="hljs-string">"files=@flows/sales-pipeline.yml"</span> \
            -F <span class="hljs-string">"namespace=sales"</span>
</code></pre>
<hr />
<h2 id="heading-community-and-support"><strong>Community and Support</strong></h2>
<h3 id="heading-getting-help"><strong>Getting Help:</strong></h3>
<ul>
<li><p><strong>Documentation</strong>: <a target="_blank" href="https://kestra.io/docs">kestra.io/docs</a></p>
</li>
<li><p><strong>GitHub Issues</strong>: <a target="_blank" href="https://github.com/kestra-io/kestra/issues">github.com/kestra-io/kestra/issues</a></p>
</li>
<li><p><strong>Community Slack</strong>: <a target="_blank" href="https://kestra.io/slack">kestra.io/slack</a> (Join 3000+ members)</p>
</li>
<li><p><strong>Stack Overflow</strong>: Use tag <code>kestra</code></p>
</li>
</ul>
<h3 id="heading-contributing"><strong>Contributing:</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Clone the repository</span>
git <span class="hljs-built_in">clone</span> https://github.com/kestra-io/kestra.git
<span class="hljs-built_in">cd</span> kestra

<span class="hljs-comment"># Build from source</span>
./gradlew build

<span class="hljs-comment"># Run tests</span>
./gradlew <span class="hljs-built_in">test</span>

<span class="hljs-comment"># Submit pull request</span>
</code></pre>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>You now have Kestra running in your chosen environment! Whether you opted for the quick Docker setup or a full Kubernetes deployment, you're ready to start building powerful data workflows.</p>
<p><strong>Key Takeaways:</strong></p>
<ol>
<li><p><strong>Start simple</strong>: Use Docker standalone for exploration</p>
</li>
<li><p><strong>Scale gradually</strong>: Move to Docker Compose, then Kubernetes</p>
</li>
<li><p><strong>Configure properly</strong>: Use environment variables and config files</p>
</li>
<li><p><strong>Monitor everything</strong>: Set up metrics and alerts from day one</p>
</li>
<li><p><strong>Join the community</strong>: The Kestra community is active and helpful</p>
</li>
</ol>
<h3 id="heading-whats-next"><strong>What's Next?</strong></h3>
<p>In the next article, we'll dive into <strong>Building Your First ETL Pipeline</strong>. We'll cover:</p>
<ul>
<li><p>Extracting data from multiple sources</p>
</li>
<li><p>Transforming with Python and SQL</p>
</li>
<li><p>Loading to databases and data warehouses</p>
</li>
<li><p>Error handling and monitoring</p>
</li>
<li><p>Best practices for production pipelines</p>
</li>
</ul>
<p><strong>Before the next article, try:</strong></p>
<ol>
<li><p>Create a namespace for your project</p>
</li>
<li><p>Build a simple flow that downloads and processes a public dataset</p>
</li>
<li><p>Explore the web UI and understand the execution view</p>
</li>
<li><p>Join the Slack community and introduce yourself</p>
</li>
</ol>
<hr />
<p><strong>Remember</strong>: The goal of installation isn't just to get software running—it's to create a foundation that's secure, scalable, and maintainable. Take the time to do it right, and your future self will thank you.</p>
<p>Happy orchestrating! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Kestra 101: Why It's Revolutionizing Data Orchestration.]]></title><description><![CDATA[From Legacy Workflow Tools to Declarative Data Pipelines
Introduction: The Orchestration Evolution
Imagine you're building a complex data pipeline. You have data scattered across cloud storage, databases, and APIs. You need to transform it, validate ...]]></description><link>https://techwasti.com/kestra-101-why-its-revolutionizing-data-orchestration</link><guid isPermaLink="true">https://techwasti.com/kestra-101-why-its-revolutionizing-data-orchestration</guid><category><![CDATA[techwasti]]></category><category><![CDATA[#Kestra]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[data engineer]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[ETL]]></category><category><![CDATA[etl-pipeline]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Tue, 03 Feb 2026 04:08:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-from-legacy-workflow-tools-to-declarative-data-pipelines">From Legacy Workflow Tools to Declarative Data Pipelines</h2>
<h3 id="heading-introduction-the-orchestration-evolution"><strong>Introduction: The Orchestration Evolution</strong></h3>
<p>Imagine you're building a complex data pipeline. You have data scattered across cloud storage, databases, and APIs. You need to transform it, validate it, and load it into a data warehouse. Oh, and it needs to run reliably at 2 AM every day, handle failures gracefully, and notify your team when something goes wrong.</p>
<p>Until recently, this meant writing hundreds of lines of Python code, managing complex DAGs (Directed Acyclic Graphs), and wrestling with scheduling systems. Enter Kestra—a paradigm shift in how we think about workflow orchestration.</p>
<h3 id="heading-the-problem-with-traditional-orchestration"><strong>The Problem with Traditional Orchestration</strong></h3>
<p>Let's face it: traditional workflow orchestration tools can be painful:</p>
<ol>
<li><p><strong>Airflow</strong>: Python code for configuration, complex scheduler, steep learning curve</p>
</li>
<li><p><strong>Prefect</strong>: More modern but still code-heavy, requires understanding of Python decorators</p>
</li>
<li><p><strong>Dagster</strong>: Development-focused but complex for simple workflows</p>
</li>
</ol>
<p>They all share a common issue: <strong>you need to be a developer to build data pipelines</strong>. This creates a bottleneck where data engineers spend more time writing orchestration code than solving data problems.</p>
<h3 id="heading-enter-kestra-the-declarative-revolution"><strong>Enter Kestra: The Declarative Revolution</strong></h3>
<p>Kestra takes a fundamentally different approach. What if instead of writing code, you could simply <strong>declare</strong> what you want to happen? What if your data pipeline looked like a recipe—clear, readable, and maintainable by anyone on your team?</p>
<p>Here's what a Kestra flow looks like:</p>
<pre><code class="lang-bash">id: daily-sales-report
namespace: finance.analytics
description: Generate daily sales report from multiple sources

tasks:
  - id: extract-data
    <span class="hljs-built_in">type</span>: io.kestra.plugin.core.http.Download
    uri: <span class="hljs-string">"https://api.company.com/sales/{{ execution.startDate | date('yyyy-MM-dd') }}"</span>

  - id: transform-data
    <span class="hljs-built_in">type</span>: io.kestra.plugin.scripts.python.Script
    script: |
      import pandas as pd
      <span class="hljs-comment"># Your transformation logic here</span>
      df = pd.read_csv(<span class="hljs-string">'sales.csv'</span>)
      df.to_parquet(<span class="hljs-string">'sales_transformed.parquet'</span>)

  - id: load-data
    <span class="hljs-built_in">type</span>: io.kestra.plugin.jdbc.snowflake.Load
    table: DAILY_SALES
    from: <span class="hljs-string">"{{ outputs.transform-data.outputFiles['sales_transformed.parquet'] }}"</span>
</code></pre>
<p>Notice something? <strong>No complex Python classes, no decorators, no infrastructure code</strong>. Just pure business logic.</p>
<h3 id="heading-why-kestra-stands-out"><strong>Why Kestra Stands Out</strong></h3>
<h4 id="heading-1-declarative-yaml-the-game-changer"><strong>1. Declarative YAML: The Game Changer</strong></h4>
<p>Kestra uses YAML to define workflows. This might seem simple, but it's revolutionary:</p>
<ul>
<li><p><strong>Human-readable</strong>: Business analysts can understand what's happening</p>
</li>
<li><p><strong>Version-controllable</strong>: Git becomes your pipeline versioning system</p>
</li>
<li><p><strong>Reusable</strong>: Components can be shared and reused across teams</p>
</li>
<li><p><strong>Auditable</strong>: Every change is tracked and reviewable</p>
</li>
</ul>
<h4 id="heading-2-no-code-vs-low-code"><strong>2. No Code vs. Low Code</strong></h4>
<p>Kestra follows a "no-code for simple tasks, low-code for complex logic" approach:</p>
<ul>
<li><p><strong>Simple tasks</strong>: HTTP calls, file operations, database queries → No code needed</p>
</li>
<li><p><strong>Complex transformations</strong>: Python, R, SQL scripts → Code where it matters</p>
</li>
<li><p><strong>Custom logic</strong>: Java plugins for enterprise needs</p>
</li>
</ul>
<h4 id="heading-3-built-in-observability"><strong>3. Built-in Observability</strong></h4>
<p>Out of the box, Kestra provides:</p>
<ul>
<li><p>Real-time execution logs</p>
</li>
<li><p>Visual flow diagrams</p>
</li>
<li><p>Performance metrics</p>
</li>
<li><p>Alerting systems</p>
</li>
<li><p>No additional setup required</p>
</li>
</ul>
<h4 id="heading-4-infinite-scalability"><strong>4. Infinite Scalability</strong></h4>
<p>Thanks to its microservices architecture, Kestra can:</p>
<ul>
<li><p>Scale horizontally to handle thousands of concurrent workflows</p>
</li>
<li><p>Run on Kubernetes for cloud-native deployments</p>
</li>
<li><p>Handle both batch and streaming workloads</p>
</li>
</ul>
<h3 id="heading-real-world-impact-case-studies"><strong>Real-World Impact: Case Studies</strong></h3>
<h4 id="heading-case-study-1-e-commerce-analytics-platform"><strong>Case Study 1: E-commerce Analytics Platform</strong></h4>
<p><strong>Problem</strong>: A retail company had 50+ Airflow DAGs that only the original authors understood. Pipeline failures took days to debug.</p>
<p><strong>Solution with Kestra</strong>:</p>
<ul>
<li><p>Converted all DAGs to YAML flows</p>
</li>
<li><p>Reduced pipeline code by 70%</p>
</li>
<li><p>Business analysts could now modify data transformations</p>
</li>
<li><p>Mean Time to Resolution (MTTR) dropped from 8 hours to 30 minutes</p>
</li>
</ul>
<h4 id="heading-case-study-2-financial-services-compliance"><strong>Case Study 2: Financial Services Compliance</strong></h4>
<p><strong>Problem</strong>: A bank needed to process millions of transactions daily with strict audit requirements.</p>
<p><strong>Solution with Kestra</strong>:</p>
<ul>
<li><p>Built compliant workflows with built-in audit trails</p>
</li>
<li><p>Implemented granular access controls</p>
</li>
<li><p>Automated regulatory reporting</p>
</li>
<li><p>Reduced manual intervention by 90%</p>
</li>
</ul>
<h3 id="heading-kestra-vs-the-competition-a-fair-comparison"><strong>Kestra vs. The Competition: A Fair Comparison</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Kestra</td><td>Airflow</td><td>Prefect</td><td>Dagster</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Configuration</strong></td><td>YAML</td><td>Python</td><td>Python</td><td>Python</td></tr>
<tr>
<td><strong>Learning Curve</strong></td><td>Low</td><td>High</td><td>Medium</td><td>High</td></tr>
<tr>
<td><strong>Observability</strong></td><td>Built-in</td><td>Plugins</td><td>Plugins</td><td>Built-in</td></tr>
<tr>
<td><strong>Scalability</strong></td><td>Kubernetes-native</td><td>Complex</td><td>Good</td><td>Good</td></tr>
<tr>
<td><strong>Developer Experience</strong></td><td>Excellent</td><td>Good</td><td>Excellent</td><td>Excellent</td></tr>
<tr>
<td><strong>Business User Friendly</strong></td><td><strong>Yes</strong></td><td>No</td><td>Limited</td><td>No</td></tr>
<tr>
<td><strong>Plugin Ecosystem</strong></td><td>Growing</td><td>Mature</td><td>Growing</td><td>Growing</td></tr>
</tbody>
</table>
</div><h3 id="heading-the-kestra-philosophy-why-it-matters"><strong>The Kestra Philosophy: Why It Matters</strong></h3>
<p>Kestra isn't just another orchestration tool—it represents a philosophical shift:</p>
<h4 id="heading-1-democratization-of-data-engineering"><strong>1. Democratization of Data Engineering</strong></h4>
<p>With Kestra, data pipelines become accessible to:</p>
<ul>
<li><p><strong>Data Analysts</strong> who understand the business logic</p>
</li>
<li><p><strong>Business Intelligence</strong> teams needing automated reports</p>
</li>
<li><p><strong>Data Scientists</strong> focusing on models, not infrastructure</p>
</li>
</ul>
<h4 id="heading-2-infrastructure-as-configuration"><strong>2. Infrastructure as Configuration</strong></h4>
<p>Your infrastructure requirements are part of your flow definition:</p>
<pre><code class="lang-bash">tasks:
  - id: heavy-processing
    <span class="hljs-built_in">type</span>: io.kestra.plugin.scripts.python.Script
    script: <span class="hljs-string">"process_large_dataset()"</span>
    taskRunner:
      <span class="hljs-built_in">type</span>: io.kestra.plugin.core.runner.Process
      memory: 8Gi
      cpu: 4
</code></pre>
<h4 id="heading-3-event-driven-by-design"><strong>3. Event-Driven by Design</strong></h4>
<p>Kestra natively supports event-driven workflows:</p>
<ul>
<li><p>Webhook triggers</p>
</li>
<li><p>Message queue listeners</p>
</li>
<li><p>File system watchers</p>
</li>
<li><p>Schedule-based executions</p>
</li>
</ul>
<h3 id="heading-getting-started-your-first-flow-in-5-minutes"><strong>Getting Started: Your First Flow in 5 Minutes</strong></h3>
<p>Let's create something practical—a data pipeline that:</p>
<ol>
<li><p>Downloads daily COVID-19 statistics</p>
</li>
<li><p>Processes the data</p>
</li>
<li><p>Sends a summary via email</p>
</li>
</ol>
<pre><code class="lang-bash">id: covid-daily-update
namespace: public.health
description: Daily COVID-19 data processing pipeline

tasks:
  <span class="hljs-comment"># Task 1: Download latest data</span>
  - id: download-covid-data
    <span class="hljs-built_in">type</span>: io.kestra.plugin.core.http.Download
    uri: <span class="hljs-string">"https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/latest/owid-covid-latest.csv"</span>

  <span class="hljs-comment"># Task 2: Process the data</span>
  - id: process-data
    <span class="hljs-built_in">type</span>: io.kestra.plugin.scripts.python.Script
    inputFiles:
      covid_data.csv: <span class="hljs-string">"{{ outputs.download-covid-data.uri }}"</span>
    script: |
      import pandas as pd

      df = pd.read_csv(<span class="hljs-string">'covid_data.csv'</span>)

      <span class="hljs-comment"># Calculate summary statistics</span>
      summary = {
          <span class="hljs-string">'total_cases'</span>: df[<span class="hljs-string">'total_cases'</span>].sum(),
          <span class="hljs-string">'total_deaths'</span>: df[<span class="hljs-string">'total_deaths'</span>].sum(),
          <span class="hljs-string">'countries_with_data'</span>: len(df),
          <span class="hljs-string">'date'</span>: pd.Timestamp.now().strftime(<span class="hljs-string">'%Y-%m-%d'</span>)
      }

      <span class="hljs-comment"># Save summary</span>
      pd.DataFrame([summary]).to_csv(<span class="hljs-string">'summary.csv'</span>, index=False)

  <span class="hljs-comment"># Task 3: Send email notification</span>
  - id: send-email
    <span class="hljs-built_in">type</span>: io.kestra.plugin.notifications.mail.MailSend
    to: <span class="hljs-string">"analytics-team@company.com"</span>
    subject: <span class="hljs-string">"COVID-19 Daily Update - {{ execution.startDate | date('yyyy-MM-dd') }}"</span>
    htmlContent: |
      &lt;h2&gt;COVID-19 Daily Summary&lt;/h2&gt;
      &lt;p&gt;Date: {{ execution.startDate | date(<span class="hljs-string">'yyyy-MM-dd'</span>) }}&lt;/p&gt;
      &lt;p&gt;Processed {{ outputs.process-data.outputFiles[<span class="hljs-string">'summary.csv'</span>] }}&lt;/p&gt;
      &lt;p&gt;Check the dashboard <span class="hljs-keyword">for</span> detailed insights.&lt;/p&gt;

triggers:
  <span class="hljs-comment"># Run daily at 6 AM UTC</span>
  - id: schedule
    <span class="hljs-built_in">type</span>: io.kestra.plugin.core.trigger.Schedule
    cron: <span class="hljs-string">"0 6 * * *"</span>
</code></pre>
<p><strong>What makes this powerful:</strong></p>
<ol>
<li><p><strong>Self-documenting</strong>: Anyone can understand what this pipeline does</p>
</li>
<li><p><strong>Maintainable</strong>: No hidden logic, everything is explicit</p>
</li>
<li><p><strong>Reliable</strong>: Built-in retry and error handling</p>
</li>
<li><p><strong>Scalable</strong>: Can process terabytes of data with the same structure</p>
</li>
</ol>
<h3 id="heading-the-technical-magic-behind-kestra"><strong>The Technical Magic Behind Kestra</strong></h3>
<p>Kestra's architecture is what makes all this possible:</p>
<ol>
<li><p><strong>Declarative Engine</strong>: Parses YAML and creates execution plans</p>
</li>
<li><p><strong>Plugin System</strong>: 100+ pre-built connectors</p>
</li>
<li><p><strong>Execution Engine</strong>: Manages task execution across workers</p>
</li>
<li><p><strong>Storage Layer</strong>: Handles artifacts, logs, and metadata</p>
</li>
<li><p><strong>UI Layer</strong>: Real-time visualization of everything</p>
</li>
</ol>
<h3 id="heading-who-should-use-kestra"><strong>Who Should Use Kestra?</strong></h3>
<h4 id="heading-perfect-for"><strong>Perfect For:</strong></h4>
<ul>
<li><p><strong>Startups</strong>: Get production-ready orchestration without the overhead</p>
</li>
<li><p><strong>Enterprise Teams</strong>: Standardize workflows across departments</p>
</li>
<li><p><strong>Data Platform Teams</strong>: Build self-service data infrastructure</p>
</li>
<li><p><strong>Consulting Firms</strong>: Deliver solutions faster to clients</p>
</li>
</ul>
<h4 id="heading-also-great-for"><strong>Also Great For:</strong></h4>
<ul>
<li><p><strong>Academic Research</strong>: Reproducible data processing pipelines</p>
</li>
<li><p><strong>DevOps Teams</strong>: Infrastructure automation workflows</p>
</li>
<li><p><strong>Marketing Teams</strong>: Automated campaign reporting</p>
</li>
<li><p><strong>Finance Departments</strong>: Automated reconciliation and reporting</p>
</li>
</ul>
<h3 id="heading-common-misconceptions-debunked"><strong>Common Misconceptions Debunked</strong></h3>
<h4 id="heading-yaml-isnt-powerful-enough-for-complex-workflows"><strong>"YAML isn't powerful enough for complex workflows"</strong></h4>
<p><strong>Reality</strong>: Kestra's YAML supports:</p>
<ul>
<li><p>Loops and conditional execution</p>
</li>
<li><p>Variables and templating</p>
</li>
<li><p>Error handling and retries</p>
</li>
<li><p>Parallel and sequential execution</p>
</li>
<li><p>Subflows and modular design</p>
</li>
</ul>
<h4 id="heading-its-just-for-simple-etl"><strong>"It's just for simple ETL"</strong></h4>
<p><strong>Reality</strong>: Kestra powers:</p>
<ul>
<li><p>Real-time streaming pipelines</p>
</li>
<li><p>Machine learning model training</p>
</li>
<li><p>Infrastructure provisioning</p>
</li>
<li><p>CI/CD pipelines</p>
</li>
<li><p>Business process automation</p>
</li>
</ul>
<h4 id="heading-its-not-enterprise-ready"><strong>"It's not enterprise-ready"</strong></h4>
<p><strong>Reality</strong>: Kestra includes:</p>
<ul>
<li><p>Role-based access control</p>
</li>
<li><p>Audit logging</p>
</li>
<li><p>High availability</p>
</li>
<li><p>LDAP/SSO integration</p>
</li>
<li><p>Multi-tenant support</p>
</li>
</ul>
<h3 id="heading-getting-hands-on-try-it-now"><strong>Getting Hands-On: Try It Now!</strong></h3>
<p>The best way to understand Kestra is to try it. Here's how:</p>
<h4 id="heading-option-1-cloud-trial-fastest"><strong>Option 1: Cloud Trial (Fastest)</strong></h4>
<ol>
<li><p>Visit <a target="_blank" href="https://demo.kestra.io/">demo.kestra.io</a></p>
</li>
<li><p>Create an account (free)</p>
</li>
<li><p>Explore example flows</p>
</li>
<li><p>Run your first pipeline in minutes</p>
</li>
</ol>
<h4 id="heading-option-2-local-installation"><strong>Option 2: Local Installation</strong></h4>
<pre><code class="lang-bash"><span class="hljs-comment"># Run with Docker</span>
docker run --rm -p 8080:8080 kestra/kestra:latest standalone

<span class="hljs-comment"># Access at http://localhost:8080</span>
</code></pre>
<h4 id="heading-option-3-follow-along"><strong>Option 3: Follow Along</strong></h4>
<p>We'll be diving deeper into installation and setup in the next article, but if you're eager to start now, the official documentation at <a target="_blank" href="https://kestra.io/docs">kestra.io/docs</a> has everything you need.</p>
<h3 id="heading-the-future-of-orchestration"><strong>The Future of Orchestration</strong></h3>
<p>Kestra represents where workflow orchestration is headed:</p>
<ol>
<li><p><strong>Declarative over Imperative</strong>: Describe what, not how</p>
</li>
<li><p><strong>Accessible over Exclusive</strong>: Tools everyone can use</p>
</li>
<li><p><strong>Integrated over Fragmented</strong>: End-to-end solutions</p>
</li>
<li><p><strong>Observable over Opaque</strong>: Complete visibility</p>
</li>
</ol>
<h3 id="heading-conclusion-why-kestra-matters-now"><strong>Conclusion: Why Kestra Matters Now</strong></h3>
<p>We're at an inflection point in data engineering. The complexity of data systems is growing exponentially, but the number of skilled data engineers isn't keeping pace. Kestra offers a solution: <strong>democratize data orchestration</strong>.</p>
<p>Whether you're:</p>
<ul>
<li><p>A data engineer tired of maintaining complex Airflow DAGs</p>
</li>
<li><p>A data analyst wanting to automate your reports</p>
</li>
<li><p>A CTO looking to scale your data infrastructure</p>
</li>
<li><p>A startup needing reliable data pipelines without a large team</p>
</li>
</ul>
<p>Kestra offers a path forward that's simpler, more maintainable, and more accessible than anything that came before.</p>
<h3 id="heading-whats-next-in-this-series"><strong>What's Next in This Series</strong></h3>
<p>In the next article, we'll dive deep into <strong>installation and setup</strong>. You'll learn:</p>
<ol>
<li><p>How to deploy Kestra in different environments</p>
</li>
<li><p>Best practices for production deployments</p>
</li>
<li><p>Integrating with your existing infrastructure</p>
</li>
<li><p>Monitoring and maintenance strategies</p>
</li>
</ol>
<p>We'll also build a complete end-to-end data pipeline that you can use as a template for your projects.</p>
<h3 id="heading-your-first-challenge"><strong>Your First Challenge</strong></h3>
<p>Before the next article, try this:</p>
<ol>
<li><p>Visit the <a target="_blank" href="https://demo.kestra.io/">Kestra demo</a></p>
</li>
<li><p>Create a simple flow that:</p>
<ul>
<li><p>Downloads a CSV file from a public URL</p>
</li>
<li><p>Logs the number of rows</p>
</li>
<li><p>Sends a mock notification</p>
</li>
</ul>
</li>
<li><p>Share your experience in the comments</p>
</li>
</ol>
<h3 id="heading-resources-to-continue-learning"><strong>Resources to Continue Learning</strong></h3>
<ol>
<li><p><a target="_blank" href="https://kestra.io/docs"><strong>Official Documentation</strong></a>: Comprehensive guides and references</p>
</li>
<li><p><a target="_blank" href="https://github.com/kestra-io/kestra"><strong>GitHub Repository</strong></a>: Source code and examples</p>
</li>
<li><p><a target="_blank" href="https://kestra.io/slack"><strong>Community Slack</strong></a>: Connect with other users</p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/c/Kestra-io"><strong>YouTube Tutorials</strong></a>: Video walkthroughs</p>
</li>
</ol>
<hr />
<p><strong>Key Takeaways:</strong></p>
<ul>
<li><p>Kestra simplifies workflow orchestration with declarative YAML</p>
</li>
<li><p>It democratizes data pipeline creation</p>
</li>
<li><p>Built-in observability reduces debugging time</p>
</li>
<li><p>Scales from simple scripts to enterprise workflows</p>
</li>
<li><p>Represents the future of data orchestration</p>
</li>
</ul>
<p><strong>Remember</strong>: The goal isn't just to learn another tool, but to adopt a better way of building data systems. Kestra isn't just changing how we orchestrate—it's changing who can orchestrate.</p>
<p>Stay tuned for the next article where we'll get our hands dirty with installation and deployment!</p>
]]></content:encoded></item><item><title><![CDATA[Deprecation Without Regret]]></title><description><![CDATA[How to remove features safely without breaking trust.
Introduction: Nothing ever really dies in software
In theory, deprecation is simple:

Mark something as deprecated

Wait

Remove it


In reality, deprecated things live far longer than expected.
O...]]></description><link>https://techwasti.com/deprecation-without-regret</link><guid isPermaLink="true">https://techwasti.com/deprecation-without-regret</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Testing]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Sun, 25 Jan 2026 18:30:31 GMT</pubDate><content:encoded><![CDATA[<p>How to remove features safely without breaking trust.</p>
<h2 id="heading-introduction-nothing-ever-really-dies-in-software">Introduction: Nothing ever really dies in software</h2>
<p>In theory, deprecation is simple:</p>
<ul>
<li><p>Mark something as deprecated</p>
</li>
<li><p>Wait</p>
</li>
<li><p>Remove it</p>
</li>
</ul>
<p>In reality, deprecated things live far longer than expected.</p>
<p>Old endpoints, fields, jobs, and configurations continue to exist because:</p>
<ul>
<li><p>Someone still depends on them</p>
</li>
<li><p>Someone forgot about them</p>
</li>
<li><p>Someone is afraid to remove them</p>
</li>
</ul>
<p>Deprecation is not about deletion.<br />It’s about <strong>ending relationships carefully</strong>.</p>
<hr />
<h2 id="heading-why-deprecation-goes-wrong-so-often">Why deprecation goes wrong so often</h2>
<p>Most teams deprecate with good intentions and poor follow-through.</p>
<p>Common reasons:</p>
<ul>
<li><p>No clear timeline</p>
</li>
<li><p>No usage visibility</p>
</li>
<li><p>No ownership</p>
</li>
<li><p>No communication</p>
</li>
</ul>
<p>As a result, deprecated features become permanent residents.</p>
<hr />
<h2 id="heading-deprecation-is-a-process-not-a-label">Deprecation is a process, not a label</h2>
<p>Marking something as deprecated is only the <strong>first step</strong>.</p>
<p>A healthy deprecation lifecycle has four phases:</p>
<ol>
<li><p>Announce</p>
</li>
<li><p>Observe</p>
</li>
<li><p>Encourage migration</p>
</li>
<li><p>Remove</p>
</li>
</ol>
<p>Skipping any step creates risk.</p>
<hr />
<h2 id="heading-phase-1-announce-clearly-and-early">Phase 1: Announce clearly and early</h2>
<p>Deprecation should never be a surprise.</p>
<p>Communicate:</p>
<ul>
<li><p>What is being deprecated</p>
</li>
<li><p>Why it’s being deprecated</p>
</li>
<li><p>What should be used instead</p>
</li>
<li><p>When it will be removed</p>
</li>
</ul>
<h3 id="heading-example-api-deprecation-notice">Example: API deprecation notice</h3>
<p>Instead of:</p>
<blockquote>
<p>“This endpoint is deprecated.”</p>
</blockquote>
<p>Say:</p>
<blockquote>
<p>“<code>/api/v1/orders</code> is deprecated. Use <code>/api/v2/orders</code>.<br />Removal planned after June 30, 2026.”</p>
</blockquote>
<p>Clarity builds trust.</p>
<hr />
<h2 id="heading-phase-2-observe-real-usage-not-assumptions">Phase 2: Observe real usage (not assumptions)</h2>
<p>Never assume something is unused.</p>
<p>Instrument and measure:</p>
<ul>
<li><p>API calls</p>
</li>
<li><p>Field access</p>
</li>
<li><p>Job execution</p>
</li>
<li><p>Query usage</p>
</li>
</ul>
<h3 id="heading-example-silent-dependency">Example: Silent dependency</h3>
<p>A field appears unused in code.</p>
<p>But:</p>
<ul>
<li><p>A report still reads it</p>
</li>
<li><p>A nightly job depends on it</p>
</li>
</ul>
<p>Without measurement, removal is guesswork.</p>
<hr />
<h2 id="heading-phase-3-encourage-migration-dont-just-wait">Phase 3: Encourage migration (don’t just wait)</h2>
<p>Waiting silently rarely works.</p>
<p>Encourage migration by:</p>
<ul>
<li><p>Logging warnings</p>
</li>
<li><p>Returning deprecation headers</p>
</li>
<li><p>Publishing examples</p>
</li>
<li><p>Offering support</p>
</li>
</ul>
<h3 id="heading-example-api-response-header">Example: API response header</h3>
<pre><code class="lang-bash">Deprecation: <span class="hljs-literal">true</span>
Sunset: 2026-06-30
</code></pre>
<p>Clients get a signal <strong>during normal usage</strong>.</p>
<hr />
<h2 id="heading-phase-4-remove-deliberately-and-confidently">Phase 4: Remove deliberately and confidently</h2>
<p>Removal should happen:</p>
<ul>
<li><p>After usage drops to near zero</p>
</li>
<li><p>After timelines are honored</p>
</li>
<li><p>During controlled releases</p>
</li>
</ul>
<p>Removal is safest when:</p>
<ul>
<li><p>Code paths are isolated</p>
</li>
<li><p>Rollback is possible</p>
</li>
<li><p>Monitoring is active</p>
</li>
</ul>
<p>Deletion should feel boring—not risky.</p>
<hr />
<h2 id="heading-example-1-deprecating-an-api-endpoint">Example 1: Deprecating an API endpoint</h2>
<p>Bad approach:</p>
<ul>
<li><p>Mark deprecated</p>
</li>
<li><p>Remove after 3 months</p>
</li>
<li><p>Hope for the best</p>
</li>
</ul>
<p>Better approach:</p>
<ol>
<li><p>Introduce new endpoint</p>
</li>
<li><p>Keep both active</p>
</li>
<li><p>Measure traffic</p>
</li>
<li><p>Communicate timelines</p>
</li>
<li><p>Remove only when safe</p>
</li>
</ol>
<p>This takes longer—but avoids incidents.</p>
<hr />
<h2 id="heading-example-2-deprecating-a-database-column">Example 2: Deprecating a database column</h2>
<p>You want to remove:</p>
<pre><code class="lang-bash">legacy_status
</code></pre>
<p>Safe process:</p>
<ol>
<li><p>Stop writing to the column</p>
</li>
<li><p>Update reads to use new field</p>
</li>
<li><p>Monitor access</p>
</li>
<li><p>Remove column later</p>
</li>
</ol>
<p>Dropping a column first is asking for trouble.</p>
<hr />
<h2 id="heading-anti-patterns-that-break-trust">Anti-patterns that break trust</h2>
<h3 id="heading-anti-pattern-1-deprecation-without-replacement">Anti-pattern 1: Deprecation without replacement</h3>
<p>Deprecating without a clear alternative creates frustration.</p>
<p>If you remove something, offer a path forward.</p>
<hr />
<h3 id="heading-anti-pattern-2-ignoring-your-own-deadlines">Anti-pattern 2: Ignoring your own deadlines</h3>
<p>Announced removal dates matter.</p>
<p>If you miss them:</p>
<ul>
<li><p>People stop trusting timelines</p>
</li>
<li><p>Deprecation warnings get ignored</p>
</li>
</ul>
<p>Only announce dates you intend to keep.</p>
<hr />
<h3 id="heading-anti-pattern-3-removing-internal-only-features">Anti-pattern 3: Removing “internal-only” features</h3>
<p>Internal features are never truly internal.</p>
<p>They are used by:</p>
<ul>
<li><p>Scripts</p>
</li>
<li><p>Admin tools</p>
</li>
<li><p>One-off jobs</p>
</li>
</ul>
<p>Treat internal consumers with the same respect.</p>
<hr />
<h2 id="heading-how-experienced-teams-handle-deprecation">How experienced teams handle deprecation</h2>
<p>They:</p>
<ul>
<li><p>Plan deprecation at design time</p>
</li>
<li><p>Track usage continuously</p>
</li>
<li><p>Communicate repeatedly</p>
</li>
<li><p>Remove with confidence</p>
</li>
</ul>
<p>They understand:</p>
<blockquote>
<p>Trust is easier to lose than to rebuild.</p>
</blockquote>
<hr />
<h2 id="heading-a-simple-deprecation-checklist">A simple deprecation checklist</h2>
<p>Before removing anything, confirm:</p>
<ol>
<li><p>Is there a replacement?</p>
</li>
<li><p>Has usage been measured?</p>
</li>
<li><p>Have consumers been informed?</p>
</li>
<li><p>Has enough time passed?</p>
</li>
<li><p>Is rollback possible?</p>
</li>
</ol>
<p>If any answer is “no”, wait.</p>
<hr />
<h2 id="heading-final-thought-deprecation-is-about-respect">Final thought: deprecation is about respect</h2>
<p>Deprecation is not about cleaning code.<br />It’s about respecting the people who depend on it.</p>
<p>Good teams don’t surprise their users.<br />Great teams help them move forward safely.</p>
<p>Remove things—but do it without regret.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Feature Flags, Rollbacks, and Damage Control.]]></title><description><![CDATA[How experienced teams ship changes safely—and recover when things go wrong
Introduction: Production will always surprise you
No matter how good your design is, production will surprise you.

Real users behave differently

Real data exposes edge cases...]]></description><link>https://techwasti.com/feature-flags-rollbacks-and-damage-control</link><guid isPermaLink="true">https://techwasti.com/feature-flags-rollbacks-and-damage-control</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Testing]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[  feature flags]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Wed, 21 Jan 2026 18:30:27 GMT</pubDate><content:encoded><![CDATA[<p><em>How experienced teams ship changes safely—and recover when things go wrong</em></p>
<h2 id="heading-introduction-production-will-always-surprise-you">Introduction: Production will always surprise you</h2>
<p>No matter how good your design is, production will surprise you.</p>
<ul>
<li><p>Real users behave differently</p>
</li>
<li><p>Real data exposes edge cases</p>
</li>
<li><p>Real traffic finds weak spots</p>
</li>
</ul>
<p>The difference between mature teams and struggling teams is not who makes fewer mistakes.<br />It’s <strong>who can recover faster</strong>.</p>
<p>Feature flags, rollbacks, and damage control are not advanced techniques.<br />They are survival tools.</p>
<hr />
<h2 id="heading-feature-flags-are-not-optional-anymore">Feature flags are not optional anymore</h2>
<p>If you deploy code that cannot be turned off, you are gambling.</p>
<p>Feature flags give you:</p>
<ul>
<li><p>Control after deployment</p>
</li>
<li><p>Separation between deploy and release</p>
</li>
<li><p>A way out when assumptions fail</p>
</li>
</ul>
<p>They don’t prevent bugs.<br />They reduce the <strong>blast radius</strong>.</p>
<hr />
<h2 id="heading-what-feature-flags-are-actually-for">What feature flags are actually for</h2>
<p>Feature flags are best used for:</p>
<ul>
<li><p>Turning new behavior on and off</p>
</li>
<li><p>Gradual rollout</p>
</li>
<li><p>A/B testing (carefully)</p>
</li>
<li><p>Emergency shutdowns</p>
</li>
</ul>
<p>They are <strong>not</strong>:</p>
<ul>
<li><p>Permanent configuration</p>
</li>
<li><p>A replacement for design</p>
</li>
<li><p>An excuse to skip testing</p>
</li>
</ul>
<hr />
<h2 id="heading-example-1-new-business-rule-rollout">Example 1: New business rule rollout</h2>
<p>You introduce a new pricing rule.</p>
<p><strong>Without feature flag:</strong></p>
<ul>
<li><p>Deploy code</p>
</li>
<li><p>Issue found</p>
</li>
<li><p>Rollback required</p>
</li>
<li><p>Database changes complicate rollback</p>
</li>
</ul>
<p><strong>With feature flag:</strong></p>
<ul>
<li><p>Deploy code (flag OFF)</p>
</li>
<li><p>Enable for 5% users</p>
</li>
<li><p>Observe metrics</p>
</li>
<li><p>Roll back instantly if needed</p>
</li>
</ul>
<p>Very different outcomes.</p>
<hr />
<h2 id="heading-the-simplest-flag-is-often-enough">The simplest flag is often enough</h2>
<p>Feature flags don’t need complex systems.</p>
<p>Sometimes a simple check is enough:</p>
<pre><code class="lang-bash"><span class="hljs-keyword">if</span> (featureFlags.isEnabled(<span class="hljs-string">"new_pricing"</span>)) {
    applyNewPricing();
} <span class="hljs-keyword">else</span> {
    applyOldPricing();
}
</code></pre>
<p>The power comes from <strong>control</strong>, not sophistication.</p>
<hr />
<h2 id="heading-rollbacks-are-part-of-the-design">Rollbacks are part of the design</h2>
<p>If rollback is painful, it won’t happen fast enough.</p>
<p>Rollbacks fail when:</p>
<ul>
<li><p>Schema changes are irreversible</p>
</li>
<li><p>Data formats change silently</p>
</li>
<li><p>Old code can’t run on new data</p>
</li>
</ul>
<h3 id="heading-rule-of-thumb">Rule of thumb</h3>
<blockquote>
<p>If you can’t roll back in minutes, you don’t have a rollback plan.</p>
</blockquote>
<hr />
<h2 id="heading-example-2-schema-change-without-rollback">Example 2: Schema change without rollback</h2>
<p>You deploy:</p>
<ul>
<li><p>New code</p>
</li>
<li><p>New schema</p>
</li>
<li><p>Data migration</p>
</li>
</ul>
<p>A bug appears.</p>
<p>Code rollback:</p>
<ul>
<li><p>Old code doesn’t understand new schema</p>
</li>
<li><p>Data is already changed</p>
</li>
</ul>
<p>Rollback fails.</p>
<p><strong>Lesson:</strong><br />Feature flags don’t save you if the data is incompatible.</p>
<hr />
<h2 id="heading-damage-control-is-a-skill-not-a-reaction">Damage control is a skill, not a reaction</h2>
<p>When something breaks in production, panic makes things worse.</p>
<p>Experienced teams follow a simple order:</p>
<ol>
<li><p>Stop the bleeding</p>
</li>
<li><p>Stabilize the system</p>
</li>
<li><p>Understand what happened</p>
</li>
<li><p>Fix forward carefully</p>
</li>
</ol>
<p>Feature flags help with step one.</p>
<hr />
<h2 id="heading-example-3-kill-switch-in-action">Example 3: Kill switch in action</h2>
<p>A background job starts consuming too much CPU.</p>
<p>Without kill switch:</p>
<ul>
<li><p>Restart servers</p>
</li>
<li><p>Scale nodes</p>
</li>
<li><p>Hope for improvement</p>
</li>
</ul>
<p>With kill switch:</p>
<ul>
<li><p>Disable the job</p>
</li>
<li><p>System stabilizes</p>
</li>
<li><p>Root cause analysis begins</p>
</li>
</ul>
<p>No heroics required.</p>
<hr />
<h2 id="heading-anti-patterns-that-reduce-safety">Anti-patterns that reduce safety</h2>
<h3 id="heading-anti-pattern-1-permanent-feature-flags">Anti-pattern 1: Permanent feature flags</h3>
<p>Flags that never get removed become:</p>
<ul>
<li><p>Dead code</p>
</li>
<li><p>Confusing logic</p>
</li>
<li><p>Maintenance burden</p>
</li>
</ul>
<p>Flags should have an expiry date.</p>
<hr />
<h3 id="heading-anti-pattern-2-flags-without-ownership">Anti-pattern 2: Flags without ownership</h3>
<p>If no one owns a flag:</p>
<ul>
<li><p>No one cleans it up</p>
</li>
<li><p>No one knows when it’s safe to remove</p>
</li>
<li><p>No one remembers why it exists</p>
</li>
</ul>
<p>Every flag needs an owner.</p>
<hr />
<h3 id="heading-anti-pattern-3-believing-flags-fix-bad-design">Anti-pattern 3: Believing flags fix bad design</h3>
<p>Feature flags cannot fix:</p>
<ul>
<li><p>Poor data models</p>
</li>
<li><p>Breaking API changes</p>
</li>
<li><p>Irreversible migrations</p>
</li>
</ul>
<p>They are seatbelts, not engines.</p>
<hr />
<h2 id="heading-gradual-rollout-beats-big-releases">Gradual rollout beats big releases</h2>
<p>Instead of:</p>
<ul>
<li>Releasing to everyone at once</li>
</ul>
<p>Prefer:</p>
<ul>
<li><p>Internal users first</p>
</li>
<li><p>Small percentage of traffic</p>
</li>
<li><p>Gradual increase</p>
</li>
</ul>
<p>Problems surface early.<br />Impact stays small.</p>
<hr />
<h2 id="heading-observability-makes-flags-useful">Observability makes flags useful</h2>
<p>Feature flags without metrics are blind.</p>
<p>You should observe:</p>
<ul>
<li><p>Error rates</p>
</li>
<li><p>Latency</p>
</li>
<li><p>Business metrics</p>
</li>
<li><p>User behavior</p>
</li>
</ul>
<p>If you can’t see the impact, you can’t control it.</p>
<hr />
<h2 id="heading-a-simple-readiness-checklist">A simple readiness checklist</h2>
<p>Before releasing a flagged feature:</p>
<ol>
<li><p>Can we turn it off instantly?</p>
</li>
<li><p>Does old code still work?</p>
</li>
<li><p>Is rollback tested?</p>
</li>
<li><p>Are metrics in place?</p>
</li>
<li><p>Who owns this flag?</p>
</li>
</ol>
<p>If any answer is “no”, pause.</p>
<hr />
<h2 id="heading-final-thought-control-beats-confidence">Final thought: control beats confidence</h2>
<p>Confidence feels good.<br />Control saves systems.</p>
<p>Feature flags, rollbacks, and damage control are not signs of weakness.<br />They are signs of experience.</p>
<p>The best teams don’t hope nothing goes wrong.<br />They prepare for when it does.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Database Changes in Live Systems.]]></title><description><![CDATA[Why schema changes break production and how experienced teams avoid it.

Introduction: Databases remember everything
You can redeploy code in minutes.You can roll back services.You can toggle feature flags.
But your database?
It remembers every decis...]]></description><link>https://techwasti.com/database-changes-in-live-systems</link><guid isPermaLink="true">https://techwasti.com/database-changes-in-live-systems</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Testing]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Sun, 18 Jan 2026 18:30:07 GMT</pubDate><content:encoded><![CDATA[<p>Why schema changes break production and how experienced teams avoid it.</p>
<p><img src="https://plus.unsplash.com/premium_vector-1734662648778-ca3ca43e7739?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A diagram of a server and a server" /></p>
<h2 id="heading-introduction-databases-remember-everything">Introduction: Databases remember everything</h2>
<p>You can redeploy code in minutes.<br />You can roll back services.<br />You can toggle feature flags.</p>
<p>But your database?</p>
<p>It remembers <strong>every decision you ever made</strong>.</p>
<p>Most production failures related to databases don’t come from performance or capacity.<br />They come from <strong>schema changes that didn’t respect existing data</strong>.</p>
<p>If APIs punish careless change, databases are unforgiving.</p>
<hr />
<h2 id="heading-why-database-changes-are-uniquely-dangerous">Why database changes are uniquely dangerous</h2>
<p>Database changes affect:</p>
<ul>
<li><p>Running code</p>
</li>
<li><p>Historical data</p>
</li>
<li><p>Batch jobs</p>
</li>
<li><p>Reports</p>
</li>
<li><p>External integrations</p>
</li>
</ul>
<p>And they do it <strong>all at once</strong>.</p>
<p>Unlike code, database changes are often:</p>
<ul>
<li><p>Irreversible</p>
</li>
<li><p>Shared across services</p>
</li>
<li><p>Hard to test realistically</p>
</li>
</ul>
<p>This is why experienced engineers treat database evolution as a slow, deliberate process.</p>
<hr />
<h2 id="heading-the-biggest-misconception-its-just-a-small-migration">The biggest misconception: “It’s just a small migration”</h2>
<p>There is no such thing as a small migration in production.</p>
<p>A single <code>ALTER TABLE</code> can:</p>
<ul>
<li><p>Lock tables</p>
</li>
<li><p>Break reads</p>
</li>
<li><p>Corrupt assumptions</p>
</li>
<li><p>Expose hidden bugs</p>
</li>
</ul>
<p>If a change touches existing data, it is not small.</p>
<hr />
<h2 id="heading-example-1-the-dangerous-not-null">Example 1: The dangerous <code>NOT NULL</code></h2>
<p>You add a new column:</p>
<pre><code class="lang-bash">ALTER TABLE orders
ADD COLUMN source_type VARCHAR(20) NOT NULL;
</code></pre>
<p>Looks reasonable.</p>
<p>But:</p>
<ul>
<li><p>Old rows don’t have this value</p>
</li>
<li><p>Inserts fail</p>
</li>
<li><p>Reads fail</p>
</li>
<li><p>Reports fail</p>
</li>
</ul>
<p>The system breaks in places you didn’t expect.</p>
<h3 id="heading-the-safer-approach-always">The safer approach (always)</h3>
<ol>
<li><p>Add the column as nullable</p>
</li>
<li><p>Deploy code that handles <code>NULL</code></p>
</li>
<li><p>Backfill existing data</p>
</li>
<li><p>Add <code>NOT NULL</code> constraint later</p>
</li>
</ol>
<p>This is not overengineering.<br />This is production hygiene.</p>
<hr />
<h2 id="heading-expand-migrate-contract-non-negotiable-pattern">Expand → Migrate → Contract (non-negotiable pattern)</h2>
<p>This pattern should be burned into memory.</p>
<h3 id="heading-step-1-expand">Step 1: Expand</h3>
<p>Add new schema elements without breaking existing ones.</p>
<pre><code class="lang-bash">ADD COLUMN source_type VARCHAR(20);
</code></pre>
<h3 id="heading-step-2-migrate">Step 2: Migrate</h3>
<p>Gradually update:</p>
<ul>
<li><p>Old data</p>
</li>
<li><p>Old code paths</p>
</li>
<li><p>Old queries</p>
</li>
</ul>
<h3 id="heading-step-3-contract">Step 3: Contract</h3>
<p>Remove or restrict only after verification.</p>
<pre><code class="lang-bash">ALTER TABLE orders
ALTER COLUMN source_type SET NOT NULL;
</code></pre>
<p>Skipping steps is how outages happen.</p>
<hr />
<h2 id="heading-example-2-renaming-a-column-dont">Example 2: Renaming a column (don’t)</h2>
<p>Renaming feels harmless:</p>
<pre><code class="lang-bash">ALTER TABLE users
RENAME COLUMN mobile TO phone;
</code></pre>
<p>But:</p>
<ul>
<li><p>Old code still queries <code>mobile</code></p>
</li>
<li><p>Old reports fail</p>
</li>
<li><p>Old scripts break silently</p>
</li>
</ul>
<h3 id="heading-safer-alternative">Safer alternative</h3>
<ol>
<li><p>Add new column <code>phone</code></p>
</li>
<li><p>Keep both in sync</p>
</li>
<li><p>Update code gradually</p>
</li>
<li><p>Remove <code>mobile</code> later</p>
</li>
</ol>
<p>Yes, it’s extra work.<br />So is recovering from production incidents.</p>
<hr />
<h2 id="heading-historical-data-is-the-real-enemy">Historical data is the real enemy</h2>
<p>Most schema changes fail because of <strong>old data</strong>, not new code.</p>
<p>Old data:</p>
<ul>
<li><p>Has missing fields</p>
</li>
<li><p>Has unexpected values</p>
</li>
<li><p>Was created under different rules</p>
</li>
</ul>
<p>Your new logic must handle:</p>
<ul>
<li><p>Nulls</p>
</li>
<li><p>Defaults</p>
</li>
<li><p>Inconsistent formats</p>
</li>
</ul>
<h3 id="heading-example-3-new-validation-on-old-data">Example 3: New validation on old data</h3>
<p>You add validation:</p>
<pre><code class="lang-bash">email must not be empty
</code></pre>
<p>But historical data contains:</p>
<pre><code class="lang-bash">email = <span class="hljs-string">""</span>
</code></pre>
<p>Now:</p>
<ul>
<li><p>Reads fail</p>
</li>
<li><p>Jobs crash</p>
</li>
<li><p>Admin screens break</p>
</li>
</ul>
<p>New rules must coexist with old reality.</p>
<hr />
<h2 id="heading-zero-downtime-migrations-are-a-discipline">Zero-downtime migrations are a discipline</h2>
<p>Zero-downtime is not a tool.<br />It’s a mindset.</p>
<p>It means:</p>
<ul>
<li><p>No locks that block traffic</p>
</li>
<li><p>No assumptions about clean data</p>
</li>
<li><p>No “quick fixes” during business hours</p>
</li>
</ul>
<p><strong>Rule:</strong><br />If a migration cannot be safely paused or rolled back, it’s not ready.</p>
<hr />
<h2 id="heading-anti-patterns-that-hurt-the-most">Anti-patterns that hurt the most</h2>
<h3 id="heading-anti-pattern-1-coupling-code-deploy-with-schema-change">Anti-pattern 1: Coupling code deploy with schema change</h3>
<p>Deploying code and schema together assumes:</p>
<ul>
<li><p>No rollback</p>
</li>
<li><p>No partial failure</p>
</li>
<li><p>Perfect timing</p>
</li>
</ul>
<p>Reality disagrees.</p>
<p>Schema first.<br />Code second.<br />Cleanup last.</p>
<hr />
<h3 id="heading-anti-pattern-2-deleting-columns-too-early">Anti-pattern 2: Deleting columns too early</h3>
<p>Columns are rarely “unused”.<br />They are just <strong>used quietly</strong>.</p>
<p>Before deleting:</p>
<ul>
<li><p>Log access</p>
</li>
<li><p>Search queries</p>
</li>
<li><p>Check reports</p>
</li>
<li><p>Wait longer than you think</p>
</li>
</ul>
<p>Deletion is easy.<br />Recovery is not.</p>
<hr />
<h3 id="heading-anti-pattern-3-one-migration-script-for-everything">Anti-pattern 3: One migration script for everything</h3>
<p>Large migrations fail mid-way.</p>
<p>Split them:</p>
<ul>
<li><p>One change per script</p>
</li>
<li><p>One responsibility per migration</p>
</li>
<li><p>Clear rollback steps</p>
</li>
</ul>
<p>Small migrations fail less catastrophically.</p>
<hr />
<h2 id="heading-how-experienced-teams-treat-database-changes">How experienced teams treat database changes</h2>
<p>They:</p>
<ul>
<li><p>Treat migrations as production code</p>
</li>
<li><p>Review them carefully</p>
</li>
<li><p>Test them on real-like data</p>
</li>
<li><p>Run them slowly and deliberately</p>
</li>
</ul>
<p>They assume:</p>
<ul>
<li><p>Data is messy</p>
</li>
<li><p>Code will be rolled back</p>
</li>
<li><p>Something will go wrong</p>
</li>
</ul>
<p>And they plan accordingly.</p>
<hr />
<h2 id="heading-a-simple-checklist-before-database-changes">A simple checklist before database changes</h2>
<p>Before touching the database, ask:</p>
<ol>
<li><p>What old data exists?</p>
</li>
<li><p>What code reads this table?</p>
</li>
<li><p>Can this change be additive?</p>
</li>
<li><p>What happens if this runs slowly?</p>
</li>
<li><p>How do we roll back safely?</p>
</li>
</ol>
<p>If rollback is unclear, stop.</p>
<hr />
<h2 id="heading-final-thought-databases-punish-arrogance">Final thought: databases punish arrogance</h2>
<p>Databases are honest.</p>
<p>They don’t care about deadlines, intentions, or confidence.<br />They only care about correctness.</p>
<p>If you respect existing data, databases will serve you well.<br />If you rush change, they will expose it—usually in production.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Evolving APIs Without Breaking Clients.]]></title><description><![CDATA[Why API changes fail in production and how experienced teams avoid it.
Read first article from series is here.
Introduction: APIs don’t belong to you anymore
The moment you publish an API, it stops being just your code.
It becomes:

Someone else’s de...]]></description><link>https://techwasti.com/evolving-apis-without-breaking-clients</link><guid isPermaLink="true">https://techwasti.com/evolving-apis-without-breaking-clients</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[vibe coding]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Mon, 12 Jan 2026 18:30:15 GMT</pubDate><content:encoded><![CDATA[<p>Why API changes fail in production and how experienced teams avoid it.</p>
<p><a target="_blank" href="https://techwasti.com/backward-compatibility-is-not-optional">Read first article from series is here.</a></p>
<h2 id="heading-introduction-apis-dont-belong-to-you-anymore">Introduction: APIs don’t belong to you anymore</h2>
<p>The moment you publish an API, it stops being just your code.</p>
<p>It becomes:</p>
<ul>
<li><p>Someone else’s dependency</p>
</li>
<li><p>Someone else’s release risk</p>
</li>
<li><p>Someone else’s production problem</p>
</li>
</ul>
<p>Most API failures don’t come from load or latency.<br />They come from <strong>unexpected change</strong>.</p>
<p>If backward compatibility is hard in general, APIs make it harder—because your consumers are often invisible to you.</p>
<hr />
<h2 id="heading-the-uncomfortable-truth-about-api-consumers">The uncomfortable truth about API consumers</h2>
<p>You rarely know:</p>
<ul>
<li><p>Who is using your API</p>
</li>
<li><p>How often they call it</p>
</li>
<li><p>What assumptions they made</p>
</li>
<li><p>How quickly they can upgrade</p>
</li>
</ul>
<p>Even inside the same company:</p>
<ul>
<li><p>Teams move on</p>
</li>
<li><p>Ownership changes</p>
</li>
<li><p>Documentation becomes outdated</p>
</li>
</ul>
<p><strong>Designing APIs means designing for uncertainty.</strong></p>
<hr />
<h2 id="heading-what-an-api-contract-really-is">What an API contract really is</h2>
<p>An API contract is not just:</p>
<ul>
<li><p>Endpoint URL</p>
</li>
<li><p>HTTP method</p>
</li>
<li><p>Request and response fields</p>
</li>
</ul>
<p>It also includes:</p>
<ul>
<li><p>Field meanings</p>
</li>
<li><p>Validation rules</p>
</li>
<li><p>Default values</p>
</li>
<li><p>Error behavior</p>
</li>
<li><p>Ordering and timing</p>
</li>
</ul>
<p>Breaking any of these can break clients—even if the JSON still “looks valid”.</p>
<hr />
<h2 id="heading-example-1-the-hidden-breaking-change">Example 1: The hidden breaking change</h2>
<p>Original response:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"total"</span>: 5
}
</code></pre>
<p>Later change:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"total"</span>: <span class="hljs-string">"5"</span>
}
</code></pre>
<p>Everything still parses.</p>
<p>But:</p>
<ul>
<li><p>A strongly typed client fails</p>
</li>
<li><p>Calculations behave incorrectly</p>
</li>
<li><p>Bugs appear far from the API</p>
</li>
</ul>
<p><strong>Lesson:</strong><br />Data type changes are breaking changes—even if they seem harmless.</p>
<hr />
<h2 id="heading-additive-changes-the-safest-way-forward">Additive changes: the safest way forward</h2>
<p>The safest API evolution strategy is <strong>addition</strong>.</p>
<p>You can safely:</p>
<ul>
<li><p>Add optional fields</p>
</li>
<li><p>Add new endpoints</p>
</li>
<li><p>Add new error codes</p>
</li>
</ul>
<p>Clients that don’t know about them simply ignore them.</p>
<h3 id="heading-example-2-adding-instead-of-modifying">Example 2: Adding instead of modifying</h3>
<p>Instead of changing:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"amount"</span>: 1000
}
</code></pre>
<p>Add:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"amount"</span>: 1000,
  <span class="hljs-string">"pricing"</span>: {
    <span class="hljs-string">"value"</span>: 1000,
    <span class="hljs-string">"currency"</span>: <span class="hljs-string">"INR"</span>
  }
}
</code></pre>
<p>Old clients keep working.<br />New clients get richer data.</p>
<p>This is boring design.<br />And boring is good.</p>
<hr />
<h2 id="heading-when-behavior-changes-version-the-api">When behavior changes, version the API</h2>
<p>If the <strong>meaning</strong> of a response changes, versioning is not optional.</p>
<h3 id="heading-example-3-business-rule-change">Example 3: Business rule change</h3>
<p>Originally:</p>
<ul>
<li>Order is created even if stock is low</li>
</ul>
<p>Later:</p>
<ul>
<li>Order fails if stock is insufficient</li>
</ul>
<p>Same endpoint. Same request. Different outcome.</p>
<p>This is a breaking change—even if the response schema is unchanged.</p>
<p><strong>Correct approach:</strong></p>
<pre><code class="lang-bash">/api/v1/orders → old behavior
/api/v2/orders → new rules
</code></pre>
<p>Versioning is not about structure.<br />It’s about behavior.</p>
<hr />
<h2 id="heading-common-api-anti-patterns-avoid-these">Common API anti-patterns (avoid these)</h2>
<h3 id="heading-anti-pattern-1-overloading-fields">Anti-pattern 1: Overloading fields</h3>
<p>Using one field to represent multiple concepts.</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"status"</span>: <span class="hljs-string">"FAILED"</span>
}
</code></pre>
<p>Does it mean:</p>
<ul>
<li><p>Validation failed?</p>
</li>
<li><p>Payment failed?</p>
</li>
<li><p>System failed?</p>
</li>
</ul>
<p>Clients guess.<br />Guessing leads to bugs.</p>
<hr />
<h3 id="heading-anti-pattern-2-making-optional-fields-mandatory">Anti-pattern 2: Making optional fields mandatory</h3>
<p>Today:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"email"</span>: <span class="hljs-string">"user@test.com"</span>
}
</code></pre>
<p>Tomorrow:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"email"</span>: <span class="hljs-string">"user@test.com"</span>,
  <span class="hljs-string">"phone"</span>: <span class="hljs-string">"9999999999"</span>
}
</code></pre>
<p>If <code>phone</code> becomes required:</p>
<ul>
<li><p>Old clients fail validation</p>
</li>
<li><p>Deployments break unexpectedly</p>
</li>
</ul>
<p><strong>Rule:</strong><br />Once optional, always optional—at least in the same version.</p>
<hr />
<h3 id="heading-anti-pattern-3-silent-validation-tightening">Anti-pattern 3: Silent validation tightening</h3>
<p>Validation changes break more clients than schema changes.</p>
<p>Example:</p>
<ul>
<li><p>Previously allowed empty string</p>
</li>
<li><p>Now rejects it</p>
</li>
</ul>
<p>Same field. Same API. Different outcome.</p>
<p>This breaks clients silently.</p>
<hr />
<h2 id="heading-be-liberal-in-what-you-accept">Be liberal in what you accept</h2>
<p>APIs should be forgiving at the edges.</p>
<p>Accept:</p>
<ul>
<li><p>Missing optional fields</p>
</li>
<li><p>Extra fields you don’t understand</p>
</li>
<li><p>Slightly older formats</p>
</li>
</ul>
<p>Reject only when:</p>
<ul>
<li><p>Data is invalid</p>
</li>
<li><p>Security is at risk</p>
</li>
<li><p>Business rules truly require it</p>
</li>
</ul>
<p>Strict input + no versioning = production pain.</p>
<hr />
<h2 id="heading-error-handling-is-part-of-the-contract">Error handling is part of the contract</h2>
<p>Changing error behavior is a breaking change.</p>
<h3 id="heading-example-4-error-response-change">Example 4: Error response change</h3>
<p>Old:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"error"</span>: <span class="hljs-string">"INVALID_REQUEST"</span>
}
</code></pre>
<p>New:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"errors"</span>: [
    { <span class="hljs-string">"code"</span>: <span class="hljs-string">"INVALID_REQUEST"</span> }
  ]
}
</code></pre>
<p>Cleaner? Yes.<br />Backward compatible? No.</p>
<p>Clients parse errors too.</p>
<hr />
<h2 id="heading-how-experienced-teams-evolve-apis-safely">How experienced teams evolve APIs safely</h2>
<p>They do three things consistently:</p>
<ol>
<li><p><strong>Design APIs as public contracts</strong></p>
</li>
<li><p><strong>Assume clients upgrade slowly</strong></p>
</li>
<li><p><strong>Measure usage before removing anything</strong></p>
</li>
</ol>
<p>They treat APIs like products—not internal functions.</p>
<hr />
<h2 id="heading-a-simple-checklist-before-changing-an-api">A simple checklist before changing an API</h2>
<p>Before you change an API, ask:</p>
<ol>
<li><p>Will old clients still work?</p>
</li>
<li><p>Are we changing behavior or just adding data?</p>
</li>
<li><p>Can this be done additively?</p>
</li>
<li><p>Do we need a new version?</p>
</li>
<li><p>How long will we support the old one?</p>
</li>
</ol>
<p>If the answer is unclear, don’t change it yet.</p>
<hr />
<h2 id="heading-final-thought-apis-fail-at-the-boundaries">Final thought: APIs fail at the boundaries</h2>
<p>Most systems don’t fail at the core logic.</p>
<p>They fail at the boundaries—where assumptions meet reality.</p>
<p>APIs are those boundaries.</p>
<p>Design them carefully, evolve them slowly, and never forget:<br />Once an API is used, it no longer belongs only to you.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Backward Compatibility Is Not Optional.]]></title><description><![CDATA[Introduction: The problem nobody plans for
Every software team likes to talk about new features.
Very few teams like to talk about old ones.

New code should never surprise old systems.

But once your software is in production, the “old” never really...]]></description><link>https://techwasti.com/backward-compatibility-is-not-optional</link><guid isPermaLink="true">https://techwasti.com/backward-compatibility-is-not-optional</guid><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[best practices]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Sat, 10 Jan 2026 16:51:31 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction-the-problem-nobody-plans-for">Introduction: The problem nobody plans for</h2>
<p>Every software team likes to talk about new features.</p>
<p>Very few teams like to talk about old ones.</p>
<blockquote>
<p>New code should never surprise old systems.</p>
</blockquote>
<p>But once your software is in production, the “old” never really goes away:</p>
<ul>
<li><p>Old API clients keep calling your service</p>
</li>
<li><p>Old data keeps sitting in your database</p>
</li>
<li><p>Old assumptions quietly influence behavior</p>
</li>
</ul>
<p>Backward compatibility is not something you add later.<br />It is something your system <strong>demands</strong> from the moment it has users.</p>
<p>If you ignore it, your software will still work—just not for long.</p>
<p><img src="https://plus.unsplash.com/premium_photo-1724088683694-bd1b9dabd59f?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A stack of rocks sitting on top of a beach" /></p>
<p><strong>Series articles</strong></p>
<p><a target="_blank" href="https://techwasti.com/evolving-apis-without-breaking-clients">https://techwasti.com/evolving-apis-without-breaking-clients</a></p>
<p><a target="_blank" href="https://techwasti.com/database-changes-in-live-systems">https://techwasti.com/database-changes-in-live-systems</a></p>
<p><a target="_blank" href="https://techwasti.com/feature-flags-rollbacks-and-damage-control">https://techwasti.com/feature-flags-rollbacks-and-damage-control</a></p>
<p><a target="_blank" href="https://techwasti.com/deprecation-without-regret">https://techwasti.com/deprecation-without-regret</a></p>
<h2 id="heading-why-backward-compatibility-is-not-optional">Why backward compatibility is not optional?</h2>
<ul>
<li><p><strong>Respect the User's Investment:</strong> Users have spent thousands of hours and dollars building on your platform. If you break their work, you aren't "innovating"—you're destroying their value.</p>
</li>
<li><p><strong>The "Clean Slate" Trap:</strong> Engineers love rewriting from scratch to fix "messy" old code. However, that old code contains thousands of bug fixes for edge cases that the new "clean" version will inevitably miss.</p>
</li>
<li><p><strong>Trust is Binary:</strong> Once a platform breaks a user's workflow, the user begins looking for an alternative. Compatibility is what keeps users "locked in" by choice rather than by force.</p>
</li>
</ul>
<h2 id="heading-what-backward-compatibility-means-in-real-life">What backward compatibility means in real life</h2>
<p>Backward compatibility means this:</p>
<blockquote>
<p>You can ship new code today without forcing everyone else to change today.</p>
</blockquote>
<p>That “everyone else” includes:</p>
<ul>
<li><p>Mobile apps users haven’t updated</p>
</li>
<li><p>Scripts written by another team years ago</p>
</li>
<li><p>Scheduled jobs that run once a month</p>
</li>
<li><p>Integrations you forgot existed</p>
</li>
</ul>
<p>If your change requires perfect coordination across all of them, it is already fragile.</p>
<hr />
<h2 id="heading-why-backward-compatibility-is-so-easy-to-underestimate">Why backward compatibility is so easy to underestimate</h2>
<p>Most breakages don’t happen because of bad intent.<br />They happen because of <strong>reasonable assumptions</strong>.</p>
<h3 id="heading-well-update-all-consumers">“We’ll update all consumers”</h3>
<p>In a small system, maybe.</p>
<p>In a real system:</p>
<ul>
<li><p>Someone is on leave</p>
</li>
<li><p>Someone misses the message</p>
</li>
<li><p>Someone rolls back</p>
</li>
</ul>
<p>One forgotten client is enough to cause failure.</p>
<hr />
<h3 id="heading-this-is-a-small-change">“This is a small change”</h3>
<p>Small changes break systems more often than big ones.</p>
<p>Why?<br />Because they don’t look dangerous.</p>
<p>A renamed field.<br />A stricter validation.<br />A default value removed.</p>
<p>Each one looks harmless. Together, they create outages.</p>
<hr />
<h2 id="heading-example-1-the-innocent-api-change">Example 1: The innocent API change</h2>
<p>You start with a simple API response:</p>
<pre><code class="lang-rust">{
  <span class="hljs-string">"status"</span>: <span class="hljs-string">"SUCCESS"</span>
}
</code></pre>
<p>Later, you want to be more expressive:</p>
<pre><code class="lang-rust">{
  <span class="hljs-string">"status"</span>: <span class="hljs-string">"SUCCESS"</span>,
  <span class="hljs-string">"reason"</span>: <span class="hljs-string">"ORDER_CREATED"</span>
}
</code></pre>
<p>This is backward compatible.<br />Old clients ignore <code>reason</code>.</p>
<p>Now imagine this instead:</p>
<pre><code class="lang-rust">{
  <span class="hljs-string">"status"</span>: <span class="hljs-string">"ORDER_CREATED"</span>
}
</code></pre>
<p>Same information. Cleaner, right?</p>
<p>Except:</p>
<ul>
<li><p>Old clients expect only <code>SUCCESS</code> or <code>FAILED</code></p>
</li>
<li><p>They don’t crash</p>
</li>
<li><p>They behave incorrectly</p>
</li>
</ul>
<p>This is worse than an error.</p>
<p><strong>Lesson:</strong><br />Changing meaning is more dangerous than changing structure.</p>
<hr />
<h2 id="heading-backward-compatibility-and-data-the-silent-risk">Backward compatibility and data: the silent risk</h2>
<p>Code changes are visible.<br />Data changes are not.</p>
<p>Once data is stored:</p>
<ul>
<li><p>It outlives deployments</p>
</li>
<li><p>It flows through new code paths</p>
</li>
<li><p>It carries old assumptions</p>
</li>
</ul>
<h3 id="heading-example-2-adding-a-mandatory-field">Example 2: Adding a mandatory field</h3>
<p>You add a new column:</p>
<pre><code class="lang-rust">source_type
</code></pre>
<p>You make it <code>NOT NULL</code> because it’s “required now”.</p>
<p>But:</p>
<ul>
<li><p>Old records don’t have it</p>
</li>
<li><p>Batch jobs read historical data</p>
</li>
<li><p>Reports start failing</p>
</li>
</ul>
<p>The system didn’t break immediately.<br />It broke slowly—and quietly.</p>
<p><strong>Safer approach:</strong></p>
<ol>
<li><p>Add the column as nullable</p>
</li>
<li><p>Handle missing values in code</p>
</li>
<li><p>Backfill old data</p>
</li>
<li><p>Enforce the constraint later</p>
</li>
</ol>
<p>Backward compatibility is often about <strong>patience</strong>, not complexity.</p>
<hr />
<h2 id="heading-the-most-common-backward-compatibility-mistake">The most common backward compatibility mistake</h2>
<p>Reusing existing fields for new meaning.</p>
<p>It feels efficient.<br />It saves time.<br />It creates long-term confusion.</p>
<h3 id="heading-example-3-status-field-abuse">Example 3: Status field abuse</h3>
<p>Originally:</p>
<pre><code class="lang-rust">status = ACTIVE | INACTIVE
</code></pre>
<p>Later:</p>
<pre><code class="lang-rust">status = ACTIVE | INACTIVE | FAILED | PENDING
</code></pre>
<p>Old code:</p>
<ul>
<li><p>Doesn’t know what <code>FAILED</code> means</p>
</li>
<li><p>Treats it as <code>ACTIVE</code></p>
</li>
<li><p>Sends wrong notifications</p>
</li>
</ul>
<p>Nothing crashes.<br />Everything is wrong.</p>
<p><strong>Rule:</strong><br />If the meaning changes, create something new.</p>
<hr />
<h2 id="heading-add-dont-change-the-safest-rule">Add, don’t change (the safest rule)</h2>
<p>The safest backward-compatible change is <strong>addition</strong>.</p>
<p>Instead of:</p>
<ul>
<li><p>Renaming fields → add new fields</p>
</li>
<li><p>Changing logic → introduce new paths</p>
</li>
<li><p>Modifying behavior → make it configurable</p>
</li>
</ul>
<h3 id="heading-example-4-evolving-pricing-logic">Example 4: Evolving pricing logic</h3>
<p>Instead of changing:</p>
<pre><code class="lang-rust">price
</code></pre>
<p>Add:</p>
<pre><code class="lang-bash">discounted_price
</code></pre>
<p>Old code keeps working.<br />New code gets better data.</p>
<p>Yes, the payload gets bigger.<br />That’s a small price to pay for stability.</p>
<hr />
<h2 id="heading-when-breaking-changes-are-unavoidable">When breaking changes are unavoidable</h2>
<p>Sometimes, breaking changes are necessary.</p>
<p>That’s okay.</p>
<p>What’s not okay is pretending they’re not breaking.</p>
<p>When you must break:</p>
<ul>
<li><p>Version it</p>
</li>
<li><p>Announce it</p>
</li>
<li><p>Support the old path for a while</p>
</li>
<li><p>Measure usage before removal</p>
</li>
</ul>
<h3 id="heading-example-5-api-versioning-done-right">Example 5: API versioning done right</h3>
<pre><code class="lang-bash">/api/v1/orders   → old behavior
/api/v2/orders   → new rules
</code></pre>
<p>This is not extra work.<br />This is respect for your consumers.</p>
<hr />
<h2 id="heading-a-simple-test-before-you-merge">A simple test before you merge</h2>
<p>Before merging a change, ask:</p>
<ol>
<li><p>What existing code depends on this?</p>
</li>
<li><p>What old data will flow through this logic?</p>
</li>
<li><p>What happens if a client doesn’t upgrade?</p>
</li>
<li><p>Can this be added instead of changed?</p>
</li>
<li><p>How do we roll back?</p>
</li>
</ol>
<p>If you can’t answer these, the change is not ready.</p>
<hr />
<h2 id="heading-final-thought-backward-compatibility-is-empathy">Final thought: backward compatibility is empathy</h2>
<p>Backward compatibility is not about being careful.</p>
<p>It’s about empathy:</p>
<ul>
<li><p>Empathy for users you’ll never meet</p>
</li>
<li><p>Empathy for teammates you don’t work with</p>
</li>
<li><p>Empathy for your future self debugging production</p>
</li>
</ul>
<blockquote>
<p>Good software doesn’t just work today. It continues working <strong>even when the past shows up</strong>.</p>
</blockquote>
<h2 id="heading-conclusion">Conclusion:</h2>
<p>Backward compatibility is not a technical checkbox.<br />It is a design attitude.</p>
<p>Most production issues don’t come from complex failures.<br />They come from simple changes that ignored the past.</p>
<p>If your software is used by real people, stores real data, or talks to other systems, then backward compatibility is already part of your job—whether you acknowledge it or not.</p>
<p>The teams that succeed long-term are not the fastest at shipping new features.<br />They are the ones that introduce change <strong>without causing damage</strong>.</p>
<p>Write new code.<br />But let it live peacefully with the old.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[The Best Way to Structure REST API Responses in Spring Boot.]]></title><description><![CDATA[Designing a clean and consistent API response structure is crucial to providing a good developer experience and ensuring maintainability. Whether you're working on a small project or a large-scale application, structuring your API responses properly ...]]></description><link>https://techwasti.com/the-best-way-to-structure-rest-api-responses-in-spring-boot</link><guid isPermaLink="true">https://techwasti.com/the-best-way-to-structure-rest-api-responses-in-spring-boot</guid><category><![CDATA[Springboot]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Java]]></category><category><![CDATA[java, java8, programming, code, coding, beginner, stream api, api, ]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Tue, 10 Dec 2024 18:30:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732847232226/85f0784d-3d34-4415-9370-7f333362a537.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Designing a clean and consistent API response structure is crucial to providing a good developer experience and ensuring maintainability. Whether you're working on a small project or a large-scale application, structuring your API responses properly can have a significant impact on the ease of consumption and debugging.</p>
<p>In this article, we'll explore why <strong>API response structure matters</strong>, and how to <strong>standardize responses</strong> in a Spring Boot application to make them more predictable, informative, and developer-friendly.</p>
<hr />
<h2 id="heading-why-api-response-structure-matters"><strong>Why API Response Structure Matters</strong></h2>
<p>When developers interact with APIs, they expect certain things to be consistent. A well-structured API response provides clarity and helps developers understand exactly what to expect when calling an endpoint. Here are some of the key reasons why structuring your API responses is important:</p>
<h3 id="heading-1-predictability"><strong>1. Predictability</strong></h3>
<p>A consistent structure allows developers to predict how to handle the response. This reduces confusion and avoids unnecessary conditional checks in the client code. For example, the same structure should be used for both successful and error responses, so developers can easily extract meaningful information.</p>
<h3 id="heading-2-informative"><strong>2. Informative</strong></h3>
<p>An API response should provide more than just the requested data. It should also include relevant metadata and context, which helps clients understand the status of the request and how to proceed next. For example, metadata like pagination or rate limits can help clients optimize their interactions with the API.</p>
<h3 id="heading-3-descriptive"><strong>3. Descriptive</strong></h3>
<p>Clear descriptions of what happened are crucial, especially in error cases. A good API response indicates whether the request was successful or not and provides relevant information such as error codes or detailed messages. This is useful for debugging and improving the client-side experience.</p>
<h3 id="heading-4-simplicity"><strong>4. Simplicity</strong></h3>
<p>Simplicity in design ensures that the response is easy to understand and work with. Overcomplicating the structure can confuse developers and increase the likelihood of errors. The goal is to avoid unnecessary complexity while still delivering all necessary information.</p>
<p>By adhering to these principles, your API responses will be more developer-friendly, maintainable, and easier to debug.</p>
<hr />
<h2 id="heading-standardizing-api-responses"><strong>Standardizing API Responses</strong></h2>
<p>A common approach to standardizing API responses is to wrap the data in a response object. This object typically includes:</p>
<ul>
<li><p><strong>Status</strong>: HTTP status codes (e.g., 200 for success, 404 for not found).</p>
</li>
<li><p><strong>Message</strong>: A short description of the outcome (e.g., "Data fetched successfully", "Resource not found").</p>
</li>
<li><p><strong>Data</strong>: The actual payload of the response (can be null for error cases).</p>
</li>
<li><p><strong>Metadata</strong>: Optional information like pagination details or rate limit information.</p>
</li>
</ul>
<p>This approach provides a clear and consistent structure that can be used for both successful and error responses.</p>
<hr />
<h2 id="heading-designing-a-response-object"><strong>Designing a Response Object</strong></h2>
<p>Let’s design a standard response format using a custom <code>ApiResponse</code> class in Spring Boot.</p>
<h3 id="heading-1-create-the-apiresponse-class"><strong>1. Create the</strong> <code>ApiResponse</code> Class</h3>
<p>This class will hold the status, message, data, and metadata for every response.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> lombok.AllArgsConstructor;
<span class="hljs-keyword">import</span> lombok.Getter;
<span class="hljs-keyword">import</span> lombok.NoArgsConstructor;
<span class="hljs-keyword">import</span> lombok.Setter;
<span class="hljs-keyword">import</span> org.springframework.http.HttpStatus;

<span class="hljs-meta">@Getter</span>
<span class="hljs-meta">@Setter</span>
<span class="hljs-meta">@NoArgsConstructor</span>
<span class="hljs-meta">@AllArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApiResponse</span>&lt;<span class="hljs-title">T</span>&gt; </span>{

    <span class="hljs-keyword">private</span> HttpStatus status;  <span class="hljs-comment">// HTTP status code (e.g., 200, 404)</span>
    <span class="hljs-keyword">private</span> String message;     <span class="hljs-comment">// Description of the outcome</span>
    <span class="hljs-keyword">private</span> T data;             <span class="hljs-comment">// The actual data (can be null for error cases)</span>
    <span class="hljs-keyword">private</span> Object metadata;    <span class="hljs-comment">// Optional metadata like pagination info</span>
}
</code></pre>
<h3 id="heading-2-handling-success-responses"><strong>2. Handling Success Responses</strong></h3>
<p>In the case of a successful request, we would wrap the data inside the <code>ApiResponse</code> object with a <code>200 OK</code> status.</p>
<pre><code class="lang-java"><span class="hljs-meta">@GetMapping("/items")</span>
<span class="hljs-keyword">public</span> ResponseEntity&lt;ApiResponse&lt;List&lt;Item&gt;&gt;&gt; getItems() {
    List&lt;Item&gt; items = itemService.getAllItems();
    ApiResponse&lt;List&lt;Item&gt;&gt; response = <span class="hljs-keyword">new</span> ApiResponse&lt;&gt;(HttpStatus.OK, <span class="hljs-string">"Items fetched successfully"</span>, items, <span class="hljs-keyword">null</span>);
    <span class="hljs-keyword">return</span> ResponseEntity.ok(response);
}
</code></pre>
<h3 id="heading-3-handling-error-responses"><strong>3. Handling Error Responses</strong></h3>
<p>For error cases, we can provide an error message and use an appropriate HTTP status code (e.g., 400 for bad requests, 404 for not found, 500 for internal errors).</p>
<pre><code class="lang-java"><span class="hljs-meta">@GetMapping("/items/{id}")</span>
<span class="hljs-keyword">public</span> ResponseEntity&lt;ApiResponse&lt;Void&gt;&gt; getItem(<span class="hljs-meta">@PathVariable("id")</span> Long id) {
    Optional&lt;Item&gt; item = itemService.getItemById(id);

    <span class="hljs-keyword">if</span> (!item.isPresent()) {
        ApiResponse&lt;Void&gt; errorResponse = <span class="hljs-keyword">new</span> ApiResponse&lt;&gt;(HttpStatus.NOT_FOUND, <span class="hljs-string">"Item not found"</span>, <span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span>);
        <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }

    ApiResponse&lt;Item&gt; successResponse = <span class="hljs-keyword">new</span> ApiResponse&lt;&gt;(HttpStatus.OK, <span class="hljs-string">"Item fetched successfully"</span>, item.get(), <span class="hljs-keyword">null</span>);
    <span class="hljs-keyword">return</span> ResponseEntity.ok(successResponse);
}
</code></pre>
<h3 id="heading-4-handling-pagination-and-metadata"><strong>4. Handling Pagination and Metadata</strong></h3>
<p>If your API supports pagination, you can include metadata like pagination details in the response.</p>
<pre><code class="lang-java"><span class="hljs-meta">@GetMapping("/items")</span>
<span class="hljs-keyword">public</span> ResponseEntity&lt;ApiResponse&lt;Page&lt;Item&gt;&gt;&gt; getItems(<span class="hljs-meta">@RequestParam</span> <span class="hljs-keyword">int</span> page, <span class="hljs-meta">@RequestParam</span> <span class="hljs-keyword">int</span> size) {
    Page&lt;Item&gt; items = itemService.getItems(PageRequest.of(page, size));
    ApiResponse&lt;Page&lt;Item&gt;&gt; response = <span class="hljs-keyword">new</span> ApiResponse&lt;&gt;(HttpStatus.OK, <span class="hljs-string">"Items fetched successfully"</span>, items, 
            <span class="hljs-keyword">new</span> PaginationMetadata(items.getTotalElements(), items.getTotalPages()));
    <span class="hljs-keyword">return</span> ResponseEntity.ok(response);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaginationMetadata</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">long</span> totalItems;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> totalPages;

    <span class="hljs-comment">// Constructor, Getters and Setters</span>
}
</code></pre>
<h3 id="heading-5-general-error-handling-with-controlleradvice"><strong>5. General Error Handling with</strong> <code>@ControllerAdvice</code></h3>
<p>For global error handling, use a <code>@ControllerAdvice</code> class to handle exceptions across all controllers. This ensures consistent error responses throughout your application.</p>
<pre><code class="lang-java"><span class="hljs-meta">@ControllerAdvice</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> </span>{

    <span class="hljs-meta">@ExceptionHandler(ResourceNotFoundException.class)</span>
    <span class="hljs-keyword">public</span> ResponseEntity&lt;ApiResponse&lt;Void&gt;&gt; handleResourceNotFoundException(ResourceNotFoundException ex) {
        ApiResponse&lt;Void&gt; response = <span class="hljs-keyword">new</span> ApiResponse&lt;&gt;(HttpStatus.NOT_FOUND, ex.getMessage(), <span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span>);
        <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }

    <span class="hljs-meta">@ExceptionHandler(Exception.class)</span>
    <span class="hljs-keyword">public</span> ResponseEntity&lt;ApiResponse&lt;Void&gt;&gt; handleGenericException(Exception ex) {
        ApiResponse&lt;Void&gt; response = <span class="hljs-keyword">new</span> ApiResponse&lt;&gt;(HttpStatus.INTERNAL_SERVER_ERROR, <span class="hljs-string">"An error occurred"</span>, <span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span>);
        <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}
</code></pre>
<hr />
<h2 id="heading-best-practices-for-api-responses"><strong>Best Practices for API Responses</strong></h2>
<ul>
<li><p><strong>Use Meaningful HTTP Status Codes</strong>: Always return the correct HTTP status code. For example, use <code>201 Created</code> for successful creation and <code>400 Bad Request</code> for invalid input.</p>
</li>
<li><p><strong>Consistent Structure</strong>: Ensure that all your responses follow the same structure, regardless of success or error. This predictability helps developers handle responses easily.</p>
</li>
<li><p><strong>Error Details</strong>: For error responses, provide meaningful error messages and if applicable, error codes for easier troubleshooting.</p>
</li>
<li><p><strong>Metadata for Pagination</strong>: When dealing with large datasets, include pagination metadata in the response to help the client manage the data efficiently.</p>
</li>
</ul>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Structuring API responses consistently is an essential practice for creating maintainable and developer-friendly APIs. By using a standardized response format that includes status, message, data, and metadata, you can make your APIs easier to consume, debug, and extend. Whether you’re handling success responses, errors, or pagination, a consistent structure ensures that clients can rely on your API’s behavior and predict the outcomes of their requests.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Dynamically Load Spring Properties from External Repositories.]]></title><description><![CDATA[Managing configurations dynamically is a crucial requirement in modern applications. In this article, we'll explore how to dynamically load Spring properties from external repositories by extending the EnvironmentPostProcessor and EnumerablePropertyS...]]></description><link>https://techwasti.com/dynamically-load-spring-properties-from-external-repositories</link><guid isPermaLink="true">https://techwasti.com/dynamically-load-spring-properties-from-external-repositories</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Java]]></category><category><![CDATA[java, java8, programming, code, coding, beginner, stream api, api, ]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Mon, 09 Dec 2024 18:30:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732847005864/04770c25-eadc-44cc-949c-023498964151.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Managing configurations dynamically is a crucial requirement in modern applications. In this article, we'll explore how to <strong>dynamically load Spring properties from external repositories</strong> by extending the <code>EnvironmentPostProcessor</code> and <code>EnumerablePropertySource</code> classes. This approach allows you to integrate external configuration sources seamlessly into your Spring application, ensuring flexibility and scalability.</p>
<hr />
<h2 id="heading-why-dynamically-load-properties"><strong>Why Dynamically Load Properties?</strong></h2>
<p>Dynamic property loading is beneficial for several scenarios:</p>
<ol>
<li><p><strong>Centralized Configuration</strong>: Applications using a central configuration repository (e.g., AWS Parameter Store, Consul, etc.) can dynamically fetch settings during startup or runtime.</p>
</li>
<li><p><strong>Environment-Specific Settings</strong>: Load properties specific to a deployment environment without modifying application code.</p>
</li>
<li><p><strong>Enhanced Security</strong>: Sensitive properties can be securely fetched from vaults or encrypted repositories.</p>
</li>
</ol>
<hr />
<h2 id="heading-key-classes-environmentpostprocessor-and-enumerablepropertysource"><strong>Key Classes:</strong> <code>EnvironmentPostProcessor</code> and <code>EnumerablePropertySource</code></h2>
<ol>
<li><p><code>EnvironmentPostProcessor</code>:</p>
<ul>
<li><p>Used to customize the Spring <code>Environment</code> before the application context is refreshed.</p>
</li>
<li><p>Ideal for adding or modifying property sources programmatically.</p>
</li>
</ul>
</li>
<li><p><code>EnumerablePropertySource</code>:</p>
<ul>
<li><p>Represents a property source with enumerable property names.</p>
</li>
<li><p>Used to define custom property sources for external repositories.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-step-by-step-implementation"><strong>Step-by-Step Implementation</strong></h2>
<h3 id="heading-1-add-dependencies"><strong>1. Add Dependencies</strong></h3>
<p>Make sure your <code>pom.xml</code> or <code>build.gradle</code> includes the required dependencies to access external repositories (e.g., for HTTP, AWS, or Consul).</p>
<h4 id="heading-example-pomxml">Example (<code>pom.xml</code>):</h4>
<pre><code class="lang-java">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<hr />
<h3 id="heading-2-implement-a-custom-environmentpostprocessor"><strong>2. Implement a Custom</strong> <code>EnvironmentPostProcessor</code></h3>
<p>The <code>EnvironmentPostProcessor</code> is invoked early in the application startup lifecycle, enabling you to register custom property sources.</p>
<h4 id="heading-example">Example:</h4>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;
<span class="hljs-keyword">import</span> org.springframework.boot.env.EnvironmentPostProcessor;
<span class="hljs-keyword">import</span> org.springframework.core.env.ConfigurableEnvironment;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomPropertyLoader</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">EnvironmentPostProcessor</span> </span>{

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">postProcessEnvironment</span><span class="hljs-params">(ConfigurableEnvironment environment, SpringApplication application)</span> </span>{
        <span class="hljs-comment">// Register the custom property source</span>
        CustomPropertySource propertySource = <span class="hljs-keyword">new</span> CustomPropertySource(<span class="hljs-string">"customProperties"</span>);
        environment.getPropertySources().addLast(propertySource);
    }
}
</code></pre>
<hr />
<h3 id="heading-3-implement-a-custom-enumerablepropertysource"><strong>3. Implement a Custom</strong> <code>EnumerablePropertySource</code></h3>
<p>The <code>EnumerablePropertySource</code> is responsible for fetching properties dynamically from an external repository.</p>
<h4 id="heading-example-1">Example:</h4>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.core.env.EnumerablePropertySource;

<span class="hljs-keyword">import</span> java.util.HashMap;
<span class="hljs-keyword">import</span> java.util.Map;
<span class="hljs-keyword">import</span> java.util.Set;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomPropertySource</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">EnumerablePropertySource</span>&lt;<span class="hljs-title">Map</span>&lt;<span class="hljs-title">String</span>, <span class="hljs-title">String</span>&gt;&gt; </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;String, String&gt; properties = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CustomPropertySource</span><span class="hljs-params">(String name)</span> </span>{
        <span class="hljs-keyword">super</span>(name);
        loadProperties();
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">loadProperties</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-comment">// Fetch properties dynamically from an external source (e.g., REST API, database)</span>
        properties.put(<span class="hljs-string">"app.dynamic.property1"</span>, <span class="hljs-string">"value1"</span>);
        properties.put(<span class="hljs-string">"app.dynamic.property2"</span>, <span class="hljs-string">"value2"</span>);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> String[] getPropertyNames() {
        Set&lt;String&gt; keys = properties.keySet();
        <span class="hljs-keyword">return</span> keys.toArray(<span class="hljs-keyword">new</span> String[<span class="hljs-number">0</span>]);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">getProperty</span><span class="hljs-params">(String name)</span> </span>{
        <span class="hljs-keyword">return</span> properties.get(name);
    }
}
</code></pre>
<hr />
<h3 id="heading-4-register-the-custom-environmentpostprocessor"><strong>4. Register the Custom</strong> <code>EnvironmentPostProcessor</code></h3>
<p>To register the <code>EnvironmentPostProcessor</code>, create a <code>spring.factories</code> file in the <code>META-INF</code> directory.</p>
<h4 id="heading-example-meta-infspringfactories">Example (<code>META-INF/spring.factories</code>):</h4>
<pre><code class="lang-java">org.springframework.boot.env.EnvironmentPostProcessor=com.example.CustomPropertyLoader
</code></pre>
<hr />
<h3 id="heading-5-access-dynamic-properties-in-your-application"><strong>5. Access Dynamic Properties in Your Application</strong></h3>
<p>Once the custom property loader is registered, you can access the dynamically loaded properties like any other Spring property.</p>
<h4 id="heading-example-applicationproperties">Example (<code>application.properties</code>):</h4>
<pre><code class="lang-java">app.dynamic.property1=${app.dynamic.property1:defaultValue1}
app.dynamic.property2=${app.dynamic.property2:defaultValue2}
</code></pre>
<h4 id="heading-example-java-code">Example (Java Code):</h4>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Value;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Component;

<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PropertyReader</span> </span>{

    <span class="hljs-meta">@Value("${app.dynamic.property1}")</span>
    <span class="hljs-keyword">private</span> String property1;

    <span class="hljs-meta">@Value("${app.dynamic.property2}")</span>
    <span class="hljs-keyword">private</span> String property2;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">printProperties</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"Dynamic Property 1: "</span> + property1);
        System.out.println(<span class="hljs-string">"Dynamic Property 2: "</span> + property2);
    }
}
</code></pre>
<hr />
<h2 id="heading-extending-the-example"><strong>Extending the Example</strong></h2>
<p>Here are some additional ideas to extend the implementation:</p>
<ol>
<li><p><strong>Fetch Properties from a REST API</strong>: Replace the <code>loadProperties()</code> method in <code>CustomPropertySource</code> to retrieve data from an external REST endpoint.</p>
<pre><code class="lang-java"> <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">loadProperties</span><span class="hljs-params">()</span> </span>{
     <span class="hljs-comment">// Simulate a REST call</span>
     properties.put(<span class="hljs-string">"api.key"</span>, <span class="hljs-string">"12345"</span>);
     properties.put(<span class="hljs-string">"api.secret"</span>, <span class="hljs-string">"secretValue"</span>);
 }
</code></pre>
</li>
<li><p><strong>Use a Vault for Secure Properties</strong>: Integrate with services like HashiCorp Vault to dynamically load secure configurations.</p>
</li>
<li><p><strong>Refresh Properties at Runtime</strong>: Combine with Spring Boot Actuator to refresh dynamic properties without restarting the application.</p>
</li>
</ol>
<hr />
<h2 id="heading-benefits-of-this-approach"><strong>Benefits of This Approach</strong></h2>
<ol>
<li><p><strong>Decoupled Configuration Management</strong>: No need to bundle all configurations with the application.</p>
</li>
<li><p><strong>Enhanced Flexibility</strong>: Dynamically load configurations based on external conditions or user needs.</p>
</li>
<li><p><strong>Improved Security</strong>: Store sensitive properties securely in external systems.</p>
</li>
</ol>
<hr />
<h2 id="heading-external-references"><strong>External References</strong></h2>
<ol>
<li><p><strong>Spring Framework Documentation</strong>:<br /> <a target="_blank" href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-environment">https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-environment</a></p>
</li>
<li><p><strong>Spring Boot External Configuration</strong>:<br /> <a target="_blank" href="https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config">https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config</a></p>
</li>
<li><p><strong>Using HashiCorp Vault with Spring Boot</strong>:<br /> <a target="_blank" href="https://spring.io/guides/gs/vault-config/">https://spring.io/guides/gs/vault-config/</a></p>
</li>
</ol>
<hr />
<p>By following this approach, you can implement a flexible, secure, and scalable solution for dynamic property management in your Spring Boot application.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Mastering Java Map Concepts: Tricky and Scenario-Based Interview Questions on HashMap, ConcurrentHashMap, SynchronizedMap, and TreeMap.]]></title><description><![CDATA[Introduction
In Java interviews, questions about the Map interface and its implementations like HashMap, ConcurrentHashMap, SynchronizedMap, and TreeMap often test both theoretical knowledge and practical problem-solving skills. This guide provides a...]]></description><link>https://techwasti.com/mastering-java-map-concepts-tricky-and-scenario-based-interview-questions-on-hashmap-concurrenthashmap-synchronizedmap-and-treemap</link><guid isPermaLink="true">https://techwasti.com/mastering-java-map-concepts-tricky-and-scenario-based-interview-questions-on-hashmap-concurrenthashmap-synchronizedmap-and-treemap</guid><category><![CDATA[Java21]]></category><category><![CDATA[Java]]></category><category><![CDATA[interview questions]]></category><category><![CDATA[coding]]></category><category><![CDATA[java, java8, programming, code, coding, beginner, stream api, api, ]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Sun, 08 Dec 2024 18:30:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732845961351/6e88b37e-b273-4aa1-95fc-8aa2a4192d15.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction"><strong>Introduction</strong></h3>
<p>In Java interviews, questions about the Map interface and its implementations like <strong>HashMap</strong>, <strong>ConcurrentHashMap</strong>, <strong>SynchronizedMap</strong>, and <strong>TreeMap</strong> often test both theoretical knowledge and practical problem-solving skills. This guide provides a mix of tricky and scenario-based questions with answers to help you ace your interview.</p>
<hr />
<h3 id="heading-tricky-questions-and-answers"><strong>Tricky Questions and Answers</strong></h3>
<h4 id="heading-1-what-happens-if-two-keys-have-the-same-hash-code-in-a-hashmap"><strong>1. What happens if two keys have the same hash code in a HashMap?</strong></h4>
<ul>
<li><strong>Answer</strong>:<br />  If two keys have the same hash code, a collision occurs. HashMap uses a <strong>linked list or binary tree</strong> (after Java 8) at the bucket location to store these entries. The entries are stored as nodes, and their equality is determined by the <code>equals()</code> method, not just the hash code.</li>
</ul>
<hr />
<h4 id="heading-2-how-does-hashmap-handle-null-keys-and-values"><strong>2. How does HashMap handle null keys and values?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:</p>
<ul>
<li><p><strong>Null Key</strong>: HashMap allows a single <code>null</code> key and stores it at index 0 of the bucket array.</p>
</li>
<li><p><strong>Null Values</strong>: Multiple null values are allowed.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-3-why-is-concurrenthashmap-preferred-over-synchronizedmap-in-multi-threaded-environments"><strong>3. Why is ConcurrentHashMap preferred over SynchronizedMap in multi-threaded environments?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:</p>
<ul>
<li><p><strong>SynchronizedMap</strong> synchronizes all methods, which leads to a performance bottleneck.</p>
</li>
<li><p><strong>ConcurrentHashMap</strong> uses a lock-striping mechanism, allowing concurrent reads and writes to different buckets, improving performance.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-4-can-a-treemap-store-null-keys-or-values"><strong>4. Can a TreeMap store null keys or values?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:</p>
<ul>
<li><p>Null <strong>keys</strong> are not allowed in TreeMap because it uses natural ordering or a comparator, and null keys can't be compared.</p>
</li>
<li><p>Null <strong>values</strong> are allowed.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-5-what-is-the-time-complexity-of-get-and-put-operations-in-hashmap"><strong>5. What is the time complexity of get() and put() operations in HashMap?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:</p>
<ul>
<li><p><strong>Best Case</strong>: O(1)O(1)O(1) if there are no collisions.</p>
</li>
<li><p><strong>Worst Case</strong>: O(log⁡n)O(\log n)O(logn) in Java 8 and later (when buckets use balanced trees).</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-scenario-based-questions-and-answers"><strong>Scenario-Based Questions and Answers</strong></h3>
<h4 id="heading-1-scenario-you-are-designing-a-cache-system-that-needs-frequent-updates-and-concurrent-readswrites-which-map-implementation-would-you-choose"><strong>1. Scenario: You are designing a cache system that needs frequent updates and concurrent reads/writes. Which map implementation would you choose?</strong></h4>
<ul>
<li><strong>Answer</strong>:<br />  Use <strong>ConcurrentHashMap</strong> because it supports high concurrency and avoids thread contention during reads and writes.</li>
</ul>
<hr />
<h4 id="heading-2-scenario-you-need-to-maintain-a-sorted-list-of-employee-ids-in-ascending-order-which-map-should-you-use"><strong>2. Scenario: You need to maintain a sorted list of employee IDs in ascending order. Which Map should you use?</strong></h4>
<ul>
<li><strong>Answer</strong>:<br />  Use a <strong>TreeMap</strong>, as it keeps the keys sorted in natural or custom order defined by a <code>Comparator</code>.</li>
</ul>
<hr />
<h4 id="heading-3-scenario-your-application-needs-a-synchronized-map-but-also-demands-frequent-iterations-which-implementation-is-better"><strong>3. Scenario: Your application needs a synchronized Map but also demands frequent iterations. Which implementation is better?</strong></h4>
<ul>
<li><strong>Answer</strong>:<br />  Use <strong>Collections.synchronizedMap(new HashMap&lt;&gt;())</strong>. However, ensure proper external synchronization when iterating to avoid <code>ConcurrentModificationException</code>. For better performance, consider using <strong>ConcurrentHashMap</strong>.</li>
</ul>
<hr />
<h4 id="heading-4-scenario-you-have-a-hashmap-storing-sensitive-data-how-would-you-ensure-thread-safety-while-maintaining-efficient-performance"><strong>4. Scenario: You have a HashMap storing sensitive data. How would you ensure thread safety while maintaining efficient performance?</strong></h4>
<ul>
<li><strong>Answer</strong>:<br />  Use <strong>ConcurrentHashMap</strong> for thread safety. If sensitive data requires stricter control, implement custom locking or atomic operations for specific use cases.</li>
</ul>
<hr />
<h3 id="heading-advanced-tricky-questions"><strong>Advanced Tricky Questions</strong></h3>
<h4 id="heading-1-how-does-resizing-work-in-hashmap"><strong>1. How does resizing work in HashMap?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:</p>
<ul>
<li><p>When the load factor exceeds the threshold, HashMap resizes by doubling the capacity.</p>
</li>
<li><p>During resizing, the entries are rehashed and redistributed into the new bucket array.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-2-what-is-the-difference-between-fail-fast-and-fail-safe-iterators-in-the-context-of-map"><strong>2. What is the difference between fail-fast and fail-safe iterators in the context of Map?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:</p>
<ul>
<li><p><strong>Fail-Fast</strong>: Iterators on HashMap, SynchronizedMap, etc., throw a <code>ConcurrentModificationException</code> if the map is structurally modified during iteration.</p>
</li>
<li><p><strong>Fail-Safe</strong>: Iterators on ConcurrentHashMap work on a <strong>copy of the map</strong> and do not throw exceptions during concurrent modifications.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-3-why-is-the-initial-capacity-and-load-factor-important-in-hashmap"><strong>3. Why is the initial capacity and load factor important in HashMap?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:</p>
<ul>
<li><p>Initial capacity reduces the need for resizing if the number of elements is predictable.</p>
</li>
<li><p>Load factor balances memory usage and performance. A higher load factor uses less memory but increases collision probability.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-coding-examples"><strong>Coding Examples</strong></h3>
<h4 id="heading-1-example-using-concurrenthashmap-for-thread-safety"><strong>1. Example: Using ConcurrentHashMap for Thread Safety</strong></h4>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> java.util.concurrent.ConcurrentHashMap;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConcurrentHashMapExample</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        ConcurrentHashMap&lt;String, String&gt; map = <span class="hljs-keyword">new</span> ConcurrentHashMap&lt;&gt;();
        map.put(<span class="hljs-string">"1"</span>, <span class="hljs-string">"Value1"</span>);
        map.put(<span class="hljs-string">"2"</span>, <span class="hljs-string">"Value2"</span>);

        <span class="hljs-comment">// Concurrently modify map</span>
        map.putIfAbsent(<span class="hljs-string">"3"</span>, <span class="hljs-string">"Value3"</span>);
        map.compute(<span class="hljs-string">"2"</span>, (key, val) -&gt; val + <span class="hljs-string">"_Modified"</span>);

        System.out.println(map);
    }
}
</code></pre>
<hr />
<h4 id="heading-2-example-using-treemap-with-custom-comparator"><strong>2. Example: Using TreeMap with Custom Comparator</strong></h4>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> java.util.*;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TreeMapExample</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        TreeMap&lt;Integer, String&gt; treeMap = <span class="hljs-keyword">new</span> TreeMap&lt;&gt;(Comparator.reverseOrder());
        treeMap.put(<span class="hljs-number">1</span>, <span class="hljs-string">"One"</span>);
        treeMap.put(<span class="hljs-number">2</span>, <span class="hljs-string">"Two"</span>);
        treeMap.put(<span class="hljs-number">3</span>, <span class="hljs-string">"Three"</span>);

        System.out.println(treeMap); <span class="hljs-comment">// Prints in descending order</span>
    }
}
</code></pre>
<hr />
<h3 id="heading-stream-and-map-tricky-questions-and-answers"><strong>Stream and Map Tricky Questions and Answers</strong></h3>
<h4 id="heading-1-how-do-you-filter-entries-in-a-map-using-streams"><strong>1. How do you filter entries in a Map using Streams?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:<br />  Use the <code>entrySet()</code> method to create a stream of <code>Map.Entry&lt;K, V&gt;</code> and apply filters.</p>
</li>
<li><p><strong>Example</strong>:</p>
<pre><code class="lang-java">  Map&lt;String, Integer&gt; map = Map.of(<span class="hljs-string">"A"</span>, <span class="hljs-number">10</span>, <span class="hljs-string">"B"</span>, <span class="hljs-number">20</span>, <span class="hljs-string">"C"</span>, <span class="hljs-number">30</span>);
  Map&lt;String, Integer&gt; filteredMap = map.entrySet().stream()
      .filter(entry -&gt; entry.getValue() &gt; <span class="hljs-number">15</span>)
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  System.out.println(filteredMap); <span class="hljs-comment">// Output: {B=20, C=30}</span>
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-2-how-can-you-sort-a-map-by-its-values-using-streams"><strong>2. How can you sort a Map by its values using Streams?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:<br />  Convert the <code>entrySet</code> to a stream, sort it using a comparator, and collect it back into a new map.</p>
</li>
<li><p><strong>Example</strong>:</p>
<pre><code class="lang-java">  Map&lt;String, Integer&gt; map = Map.of(<span class="hljs-string">"A"</span>, <span class="hljs-number">30</span>, <span class="hljs-string">"B"</span>, <span class="hljs-number">10</span>, <span class="hljs-string">"C"</span>, <span class="hljs-number">20</span>);
  Map&lt;String, Integer&gt; sortedMap = map.entrySet().stream()
      .sorted(Map.Entry.comparingByValue())
      .collect(Collectors.toMap(
          Map.Entry::getKey, 
          Map.Entry::getValue, 
          (e1, e2) -&gt; e1, 
          LinkedHashMap::<span class="hljs-keyword">new</span>));
  System.out.println(sortedMap); <span class="hljs-comment">// Output: {B=10, C=20, A=30}</span>
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-3-how-do-you-convert-a-list-to-a-map-using-streams"><strong>3. How do you convert a List to a Map using Streams?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:<br />  Use <code>Collectors.toMap()</code> to transform a list into a map.</p>
</li>
<li><p><strong>Example</strong>:</p>
<pre><code class="lang-java">  List&lt;String&gt; names = List.of(<span class="hljs-string">"Alice"</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-string">"Charlie"</span>);
  Map&lt;String, Integer&gt; nameLengthMap = names.stream()
      .collect(Collectors.toMap(name -&gt; name, name -&gt; name.length()));
  System.out.println(nameLengthMap); <span class="hljs-comment">// Output: {Alice=5, Bob=3, Charlie=7}</span>
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-4-what-happens-if-duplicate-keys-are-encountered-while-collecting-a-stream-into-a-map"><strong>4. What happens if duplicate keys are encountered while collecting a Stream into a Map?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:<br />  A <code>DuplicateKeyException</code> is thrown unless a merge function is specified.</p>
</li>
<li><p><strong>Example</strong>:</p>
<pre><code class="lang-java">  List&lt;String&gt; names = List.of(<span class="hljs-string">"Alice"</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-string">"Alice"</span>);
  Map&lt;String, Integer&gt; nameCount = names.stream()
      .collect(Collectors.toMap(
          name -&gt; name, 
          name -&gt; <span class="hljs-number">1</span>, 
          Integer::sum)); <span class="hljs-comment">// Merges duplicate keys</span>
  System.out.println(nameCount); <span class="hljs-comment">// Output: {Alice=2, Bob=1}</span>
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-5-how-do-you-group-a-list-into-a-map-using-streams"><strong>5. How do you group a List into a Map using Streams?</strong></h4>
<ul>
<li><p><strong>Answer</strong>:<br />  Use <code>Collectors.groupingBy()</code> to group elements based on a classifier function.</p>
</li>
<li><p><strong>Example</strong>:</p>
<pre><code class="lang-java">  List&lt;String&gt; names = List.of(<span class="hljs-string">"Alice"</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-string">"Charlie"</span>, <span class="hljs-string">"Anna"</span>);
  Map&lt;Character, List&lt;String&gt;&gt; groupedByInitial = names.stream()
      .collect(Collectors.groupingBy(name -&gt; name.charAt(<span class="hljs-number">0</span>)));
  System.out.println(groupedByInitial); <span class="hljs-comment">// Output: {A=[Alice, Anna], B=[Bob], C=[Charlie]}</span>
</code></pre>
</li>
</ul>
<hr />
<h3 id="heading-scenario-based-questions"><strong>Scenario-Based Questions</strong></h3>
<h4 id="heading-1-scenario-you-have-a-map-of-products-and-their-prices-you-need-to-create-a-new-map-that-applies-a-discount-to-all-products-costing-more-than-50"><strong>1. Scenario: You have a Map of products and their prices. You need to create a new Map that applies a discount to all products costing more than $50.</strong></h4>
<ul>
<li><p><strong>Solution</strong>:</p>
<pre><code class="lang-java">  Map&lt;String, Double&gt; products = Map.of(<span class="hljs-string">"Laptop"</span>, <span class="hljs-number">800.0</span>, <span class="hljs-string">"Mouse"</span>, <span class="hljs-number">20.0</span>, <span class="hljs-string">"Keyboard"</span>, <span class="hljs-number">100.0</span>);
  Map&lt;String, Double&gt; discountedProducts = products.entrySet().stream()
      .map(entry -&gt; entry.getValue() &gt; <span class="hljs-number">50</span> 
          ? <span class="hljs-keyword">new</span> AbstractMap.SimpleEntry&lt;&gt;(entry.getKey(), entry.getValue() * <span class="hljs-number">0.9</span>) 
          : entry)
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  System.out.println(discountedProducts); <span class="hljs-comment">// Output: {Laptop=720.0, Mouse=20.0, Keyboard=90.0}</span>
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-2-scenario-combine-two-maps-into-one-summing-the-values-of-matching-keys"><strong>2. Scenario: Combine two Maps into one, summing the values of matching keys.</strong></h4>
<ul>
<li><p><strong>Solution</strong>:</p>
<pre><code class="lang-java">  Map&lt;String, Integer&gt; map1 = Map.of(<span class="hljs-string">"A"</span>, <span class="hljs-number">10</span>, <span class="hljs-string">"B"</span>, <span class="hljs-number">20</span>, <span class="hljs-string">"C"</span>, <span class="hljs-number">30</span>);
  Map&lt;String, Integer&gt; map2 = Map.of(<span class="hljs-string">"B"</span>, <span class="hljs-number">15</span>, <span class="hljs-string">"C"</span>, <span class="hljs-number">25</span>, <span class="hljs-string">"D"</span>, <span class="hljs-number">35</span>);
  Map&lt;String, Integer&gt; combinedMap = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
      .collect(Collectors.toMap(
          Map.Entry::getKey, 
          Map.Entry::getValue, 
          Integer::sum));
  System.out.println(combinedMap); <span class="hljs-comment">// Output: {A=10, B=35, C=55, D=35}</span>
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-3-scenario-convert-a-map-to-a-list-of-strings-in-the-format-keyvalue"><strong>3. Scenario: Convert a Map to a List of strings in the format "key=value".</strong></h4>
<ul>
<li><p><strong>Solution</strong>:</p>
<pre><code class="lang-java">  Map&lt;String, Integer&gt; map = Map.of(<span class="hljs-string">"A"</span>, <span class="hljs-number">10</span>, <span class="hljs-string">"B"</span>, <span class="hljs-number">20</span>, <span class="hljs-string">"C"</span>, <span class="hljs-number">30</span>);
  List&lt;String&gt; list = map.entrySet().stream()
      .map(entry -&gt; entry.getKey() + <span class="hljs-string">"="</span> + entry.getValue())
      .collect(Collectors.toList());
  System.out.println(list); <span class="hljs-comment">// Output: [A=10, B=20, C=30]</span>
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-4-scenario-partition-a-map-into-two-groups-based-on-a-condition"><strong>4. Scenario: Partition a Map into two groups based on a condition.</strong></h4>
<ul>
<li><p><strong>Solution</strong>:</p>
<pre><code class="lang-java">  Map&lt;String, Integer&gt; scores = Map.of(<span class="hljs-string">"Alice"</span>, <span class="hljs-number">85</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-number">65</span>, <span class="hljs-string">"Charlie"</span>, <span class="hljs-number">95</span>);
  Map&lt;Boolean, Map&lt;String, Integer&gt;&gt; partitioned = scores.entrySet().stream()
      .collect(Collectors.partitioningBy(
          entry -&gt; entry.getValue() &gt;= <span class="hljs-number">70</span>, 
          Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
  System.out.println(partitioned); 
  <span class="hljs-comment">// Output: {true={Alice=85, Charlie=95}, false={Bob=65}}</span>
</code></pre>
</li>
</ul>
<hr />
<h3 id="heading-final-note"><strong>Final Note</strong></h3>
<p>Using Maps with Streams provides a concise and elegant way to perform operations that would otherwise require verbose loops. The examples and questions above should prepare you to confidently discuss and demonstrate your knowledge of Maps and Streams in any interview.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Distributed Tracing with OpenTelemetry and Spring Boot 3!]]></title><description><![CDATA[As microservices become the backbone of modern applications, understanding and monitoring the flow of requests across distributed systems is crucial. Distributed tracing helps visualize this flow, diagnose bottlenecks, and debug issues effectively. O...]]></description><link>https://techwasti.com/distributed-tracing-with-opentelemetry-and-spring-boot-3</link><guid isPermaLink="true">https://techwasti.com/distributed-tracing-with-opentelemetry-and-spring-boot-3</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Java]]></category><category><![CDATA[Java21]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Sat, 07 Dec 2024 18:30:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732845068344/6e94abc7-3aa1-4790-884f-a1555364c9a3.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As microservices become the backbone of modern applications, understanding and monitoring the flow of requests across distributed systems is crucial. <strong>Distributed tracing</strong> helps visualize this flow, diagnose bottlenecks, and debug issues effectively. <strong>OpenTelemetry</strong>, an open-source observability framework, is a powerful tool to implement distributed tracing in <strong>Spring Boot 3</strong> applications.</p>
<p>In this guide, you’ll learn:</p>
<ul>
<li><p>What distributed tracing and OpenTelemetry are.</p>
</li>
<li><p>How to integrate OpenTelemetry with a Spring Boot 3 application.</p>
</li>
<li><p>How to visualize trace data using tools like Jaeger or Zipkin.</p>
</li>
</ul>
<hr />
<h2 id="heading-what-is-distributed-tracing">What Is Distributed Tracing?</h2>
<p>Distributed tracing tracks a request as it travels through multiple services in a distributed system.</p>
<h3 id="heading-key-benefits">Key Benefits:</h3>
<ul>
<li><p>Visualizing end-to-end request flows.</p>
</li>
<li><p>Pinpointing performance bottlenecks.</p>
</li>
<li><p>Identifying errors in complex microservice architectures.</p>
</li>
</ul>
<p>A <strong>trace</strong> consists of:</p>
<ul>
<li><p><strong>Spans</strong>: Individual units of work.</p>
</li>
<li><p><strong>Context</strong>: Information shared between spans.</p>
</li>
</ul>
<hr />
<h2 id="heading-what-is-opentelemetry">What Is OpenTelemetry?</h2>
<p><strong>OpenTelemetry (OTel)</strong> is an observability framework that standardizes the collection, processing, and exporting of telemetry data (traces, metrics, and logs).</p>
<h3 id="heading-why-use-opentelemetry">Why Use OpenTelemetry?</h3>
<ul>
<li><p>Vendor-neutral and supports multiple backends like <strong>Jaeger</strong>, <strong>Zipkin</strong>, <strong>Prometheus</strong>, etc.</p>
</li>
<li><p>Integrates seamlessly with Spring Boot 3.</p>
</li>
<li><p>Provides instrumentation libraries for popular technologies.</p>
</li>
</ul>
<hr />
<h2 id="heading-setting-up-opentelemetry-with-spring-boot-3">Setting Up OpenTelemetry with Spring Boot 3</h2>
<p>Follow these steps to implement distributed tracing in your Spring Boot 3 application.</p>
<hr />
<h3 id="heading-1-add-dependencies">1. Add Dependencies</h3>
<p>Include the following dependencies in your <code>pom.xml</code>:</p>
<pre><code class="lang-java">&lt;dependency&gt;
    &lt;groupId&gt;io.opentelemetry.instrumentation&lt;/groupId&gt;
    &lt;artifactId&gt;opentelemetry-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;<span class="hljs-number">1.27</span>.<span class="hljs-number">0</span>&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
    &lt;artifactId&gt;opentelemetry-exporter-otlp&lt;/artifactId&gt;
    &lt;version&gt;<span class="hljs-number">1.27</span>.<span class="hljs-number">0</span>&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<ul>
<li><p>The <code>opentelemetry-spring-boot-starter</code> provides auto-configuration for tracing.</p>
</li>
<li><p>The <code>opentelemetry-exporter-otlp</code> sends trace data to backends like Jaeger or Zipkin.</p>
</li>
</ul>
<hr />
<h3 id="heading-2-configure-opentelemetry">2. Configure OpenTelemetry</h3>
<p>Add the required configuration in <a target="_blank" href="http://application.properties"><code>application.properties</code></a>:</p>
<pre><code class="lang-java"># Exporter settings
otel.traces.exporter=otlp
otel.exporter.otlp.endpoint=http:<span class="hljs-comment">//localhost:4317</span>
otel.resource.attributes=service.name=MySpringBootApp

# Sampling configuration
otel.traces.sampler=always_on
</code></pre>
<ul>
<li>Replace <a target="_blank" href="http://localhost:4317"><code>http://localhost:4317</code></a> with the endpoint of your tracing backend.</li>
</ul>
<hr />
<h3 id="heading-3-instrument-your-application">3. Instrument Your Application</h3>
<p>With OpenTelemetry, most Spring Boot components are auto-instrumented, but you can add custom spans for detailed tracing.</p>
<h4 id="heading-example-adding-a-custom-span">Example: Adding a Custom Span</h4>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> io.opentelemetry.api.trace.Span;
<span class="hljs-keyword">import</span> io.opentelemetry.api.trace.Tracer;
<span class="hljs-keyword">import</span> io.opentelemetry.api.GlobalOpenTelemetry;

<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping("/orders")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Tracer tracer = GlobalOpenTelemetry.getTracer(<span class="hljs-string">"OrderService"</span>);

    <span class="hljs-meta">@GetMapping("/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getOrder</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> String id)</span> </span>{
        Span span = tracer.spanBuilder(<span class="hljs-string">"getOrder"</span>).startSpan();
        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// Business logic</span>
            span.setAttribute(<span class="hljs-string">"order.id"</span>, id);
            <span class="hljs-keyword">return</span> <span class="hljs-string">"Order details for ID: "</span> + id;
        } <span class="hljs-keyword">finally</span> {
            span.end();
        }
    }
}
</code></pre>
<hr />
<h3 id="heading-4-visualize-trace-data">4. Visualize Trace Data</h3>
<h4 id="heading-install-jaeger-or-zipkin">Install Jaeger or Zipkin</h4>
<p>Run Jaeger in Docker:</p>
<pre><code class="lang-java">docker run -d --name jaeger \
  -e COLLECTOR_OTLP_ENABLED=<span class="hljs-keyword">true</span> \
  -p <span class="hljs-number">16686</span>:<span class="hljs-number">16686</span> \
  -p <span class="hljs-number">4317</span>:<span class="hljs-number">4317</span> \
  jaegertracing/all-in-one:latest
</code></pre>
<ul>
<li>Access Jaeger UI at <a target="_blank" href="http://localhost:16686"><code>http://localhost:16686</code></a>.</li>
</ul>
<hr />
<h3 id="heading-5-test-the-setup">5. Test the Setup</h3>
<p>Run your Spring Boot application and make a request:</p>
<pre><code class="lang-java">curl http:<span class="hljs-comment">//localhost:8080/orders/123</span>
</code></pre>
<p>Go to the Jaeger UI to visualize the trace. You should see the spans and their relationships.</p>
<hr />
<h2 id="heading-advanced-configurations">Advanced Configurations</h2>
<h3 id="heading-use-context-propagation-across-services">Use Context Propagation Across Services</h3>
<p>In microservices, trace context (e.g., trace ID, span ID) must be propagated. OpenTelemetry handles this automatically with HTTP headers like:</p>
<ul>
<li><p><code>traceparent</code>: Carries trace information.</p>
</li>
<li><p><code>tracestate</code>: Contains vendor-specific trace data.</p>
</li>
</ul>
<hr />
<h3 id="heading-integrate-metrics-and-logs">Integrate Metrics and Logs</h3>
<p>You can also collect and export <strong>metrics</strong> and <strong>logs</strong> alongside traces using OpenTelemetry libraries for unified observability.</p>
<hr />
<h2 id="heading-benefits-of-distributed-tracing-with-opentelemetry">Benefits of Distributed Tracing with OpenTelemetry</h2>
<ol>
<li><p><strong>Vendor-Neutral</strong>: Flexibility to choose observability backends.</p>
</li>
<li><p><strong>Reduced Complexity</strong>: Auto-instrumentation simplifies setup.</p>
</li>
<li><p><strong>End-to-End Visibility</strong>: Identify bottlenecks and failures across services.</p>
</li>
</ol>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>Distributed tracing is essential for monitoring modern microservices. With <strong>OpenTelemetry</strong> and <strong>Spring Boot 3</strong>, implementing tracing is both straightforward and effective. By visualizing traces in tools like Jaeger or Zipkin, you can gain deep insights into your application's behavior and resolve issues faster.</p>
<p>Start integrating OpenTelemetry in your Spring Boot applications today and take your observability to the next level!</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Streamline Your Spring Boot API with Enums and the Strategy Pattern!]]></title><description><![CDATA[Enums in Java are an invaluable resource for simplifying code, enhancing readability, and maintaining type safety in your applications. By leveraging enums alongside the Strategy Pattern, you can reduce complex conditional logic like if-else and swit...]]></description><link>https://techwasti.com/streamline-your-spring-boot-api-with-enums-and-the-strategy-pattern</link><guid isPermaLink="true">https://techwasti.com/streamline-your-spring-boot-api-with-enums-and-the-strategy-pattern</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Java21]]></category><category><![CDATA[Java]]></category><category><![CDATA[enum]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><category><![CDATA[coding]]></category><category><![CDATA[Microservices]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Fri, 06 Dec 2024 18:30:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732844569673/5854d388-7547-409b-8837-c2a0a453e1a1.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Enums in Java are an invaluable resource for simplifying code, enhancing readability, and maintaining type safety in your applications. By leveraging enums alongside the <strong>Strategy Pattern</strong>, you can reduce complex conditional logic like <code>if-else</code> and <code>switch</code> statements, resulting in a more modular, maintainable, and error-resistant Spring Boot API.</p>
<p>In this article, you'll learn:</p>
<ul>
<li><p>What Java Enums are and their benefits.</p>
</li>
<li><p>How to use Enums with the Strategy Pattern to streamline your API logic.</p>
</li>
<li><p>A practical example of using Enums and the Strategy Pattern in a Spring Boot application.</p>
</li>
</ul>
<hr />
<h2 id="heading-why-use-enums-in-java">Why Use Enums in Java?</h2>
<p><strong>Enums</strong> (short for enumerations) are special Java types used to define a set of constants. They:</p>
<ul>
<li><p>Provide <strong>type safety</strong>, ensuring only valid values are used.</p>
</li>
<li><p>Reduce errors by centralizing constant definitions.</p>
</li>
<li><p>Make code more <strong>readable and maintainable</strong> by eliminating hard-coded strings or numbers.</p>
</li>
</ul>
<h3 id="heading-example-of-an-enum">Example of an Enum:</h3>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">OrderStatus</span> </span>{
    PENDING,
    COMPLETED,
    CANCELLED
}
</code></pre>
<p>Enums can also contain methods, fields, and constructors, making them highly versatile for managing application behavior.</p>
<hr />
<h2 id="heading-what-is-the-strategy-pattern">What Is the Strategy Pattern?</h2>
<p>The <strong>Strategy Pattern</strong> is a behavioral design pattern that enables selecting an algorithm or behavior dynamically at runtime. Instead of using conditional statements to decide the logic, you encapsulate each behavior in a separate class and let the application decide which one to use.</p>
<p><strong>When combined with enums</strong>, this pattern becomes even more powerful, as enums can map directly to specific strategies.</p>
<hr />
<h2 id="heading-example-using-enums-and-the-strategy-pattern-in-spring-boot">Example: Using Enums and the Strategy Pattern in Spring Boot</h2>
<p>Let’s create an example where we process orders based on their status (<code>PENDING</code>, <code>COMPLETED</code>, or <code>CANCELLED</code>) using enums and the Strategy Pattern.</p>
<hr />
<h3 id="heading-1-define-the-enum">1. Define the Enum</h3>
<p>Each enum constant will map to a specific strategy for handling orders:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">OrderStatus</span> </span>{
    PENDING {
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processOrder</span><span class="hljs-params">()</span> </span>{
            System.out.println(<span class="hljs-string">"Processing a pending order..."</span>);
        }
    },
    COMPLETED {
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processOrder</span><span class="hljs-params">()</span> </span>{
            System.out.println(<span class="hljs-string">"Order already completed."</span>);
        }
    },
    CANCELLED {
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processOrder</span><span class="hljs-params">()</span> </span>{
            System.out.println(<span class="hljs-string">"Cancelling the order..."</span>);
        }
    };

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processOrder</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<p>Key Points:</p>
<ul>
<li><p>Each constant overrides the <code>processOrder</code> method.</p>
</li>
<li><p>This eliminates the need for a <code>switch</code> statement elsewhere in the code.</p>
</li>
</ul>
<hr />
<h3 id="heading-2-define-the-strategy-interface-optional">2. Define the Strategy Interface (Optional)</h3>
<p>If the logic for each status becomes complex, you can decouple it into separate strategy classes.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">OrderProcessingStrategy</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">process</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<hr />
<h3 id="heading-3-implement-strategies-for-each-order-status">3. Implement Strategies for Each Order Status</h3>
<p>Separate the behavior into classes:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PendingOrderStrategy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OrderProcessingStrategy</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">process</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"Processing a pending order..."</span>);
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CompletedOrderStrategy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OrderProcessingStrategy</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">process</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"Order already completed."</span>);
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CancelledOrderStrategy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OrderProcessingStrategy</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">process</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"Cancelling the order..."</span>);
    }
}
</code></pre>
<hr />
<h3 id="heading-4-map-strategies-to-enum-constants">4. Map Strategies to Enum Constants</h3>
<p>The enum maps directly to strategies:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">OrderStatus</span> </span>{
    PENDING(<span class="hljs-keyword">new</span> PendingOrderStrategy()),
    COMPLETED(<span class="hljs-keyword">new</span> CompletedOrderStrategy()),
    CANCELLED(<span class="hljs-keyword">new</span> CancelledOrderStrategy());

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> OrderProcessingStrategy strategy;

    OrderStatus(OrderProcessingStrategy strategy) {
        <span class="hljs-keyword">this</span>.strategy = strategy;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processOrder</span><span class="hljs-params">()</span> </span>{
        strategy.process();
    }
}
</code></pre>
<hr />
<h3 id="heading-5-service-layer">5. Service Layer</h3>
<p>Use the enum to process orders dynamically:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.stereotype.Service;

<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderService</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processOrder</span><span class="hljs-params">(OrderStatus status)</span> </span>{
        status.processOrder();
    }
}
</code></pre>
<hr />
<h3 id="heading-6-controller-layer">6. Controller Layer</h3>
<p>Expose an API endpoint to handle orders:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.*;

<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping("/orders")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> OrderService orderService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">OrderController</span><span class="hljs-params">(OrderService orderService)</span> </span>{
        <span class="hljs-keyword">this</span>.orderService = orderService;
    }

    <span class="hljs-meta">@PostMapping("/process/{status}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">processOrder</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> OrderStatus status)</span> </span>{
        orderService.processOrder(status);
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Order processed with status: "</span> + status;
    }
}
</code></pre>
<hr />
<h2 id="heading-testing-the-api">Testing the API</h2>
<ol>
<li><p>Start your Spring Boot application.</p>
</li>
<li><p>Use Postman or curl to test the endpoint:</p>
<ul>
<li><p><strong>Process a pending order</strong>:</p>
<pre><code class="lang-java">  POST /orders/process/PENDING
</code></pre>
</li>
<li><p><strong>Complete an order</strong>:</p>
<pre><code class="lang-java">  POST /orders/process/COMPLETED
</code></pre>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-benefits-of-using-enums-with-the-strategy-pattern">Benefits of Using Enums with the Strategy Pattern</h2>
<ol>
<li><p><strong>Code Readability</strong>: Logic for each status is encapsulated in one place, avoiding sprawling conditional statements.</p>
</li>
<li><p><strong>Extensibility</strong>: Adding a new status or behavior is as simple as adding another enum constant and strategy.</p>
</li>
<li><p><strong>Testability</strong>: Each strategy can be tested independently, making the codebase more modular.</p>
</li>
</ol>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>Enums and the Strategy Pattern are a powerful combination for simplifying logic in Spring Boot applications. By encapsulating behavior within enums or dedicated strategy classes, you can create clean, maintainable, and extensible code. Start using this approach in your projects to handle complex workflows with ease and precision!</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Effortlessly Create DTOs with Java Records and MapStruct in Spring Boot!]]></title><description><![CDATA[In modern Spring Boot applications, Data Transfer Objects (DTOs) play a vital role in ensuring clean code, maintaining separation of concerns, and improving application security. With the advent of Java Records, creating immutable DTOs has become sim...]]></description><link>https://techwasti.com/effortlessly-create-dtos-with-java-records-and-mapstruct-in-spring-boot</link><guid isPermaLink="true">https://techwasti.com/effortlessly-create-dtos-with-java-records-and-mapstruct-in-spring-boot</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Java]]></category><category><![CDATA[Java21]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Spring Data Jpa]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Thu, 05 Dec 2024 18:30:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732844072722/830e6348-2c29-45ba-9650-121c4702fea9.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In modern Spring Boot applications, <strong>Data Transfer Objects (DTOs)</strong> play a vital role in ensuring clean code, maintaining separation of concerns, and improving application security. With the advent of <strong>Java Records</strong>, creating immutable DTOs has become simpler than ever. When paired with <strong>MapStruct</strong>, an efficient mapping framework, handling object conversions becomes a breeze.</p>
<p>In this article, you’ll learn:</p>
<ul>
<li><p>What Java Records are and why they’re great for DTOs.</p>
</li>
<li><p>How to use MapStruct to map entities to DTOs and vice versa.</p>
</li>
<li><p>A complete example showcasing DTOs with Records and MapStruct in Spring Boot.</p>
</li>
</ul>
<hr />
<h2 id="heading-what-are-java-records">What Are Java Records?</h2>
<p>Java Records, introduced in <strong>Java 14</strong> (as a preview) and officially in <strong>Java 16</strong>, are a concise way to declare classes that are primarily used to store data. They are:</p>
<ul>
<li><p><strong>Immutable</strong>: Fields of records cannot be changed after the object is created.</p>
</li>
<li><p><strong>Boilerplate-Free</strong>: Automatically generates constructors, getters, <code>equals()</code>, <code>hashCode()</code>, and <code>toString()</code> methods.</p>
</li>
</ul>
<p>Example of a Record:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">CustomerDTO</span><span class="hljs-params">(Long id, String name, String email)</span> </span>{}
</code></pre>
<p>Why use Records for DTOs?</p>
<ul>
<li><p><strong>Immutability</strong> ensures data integrity.</p>
</li>
<li><p><strong>Compact Syntax</strong> reduces clutter, focusing on the data.</p>
</li>
</ul>
<hr />
<h2 id="heading-what-is-mapstruct">What Is MapStruct?</h2>
<p><strong>MapStruct</strong> is a Java-based code generator that simplifies mapping between objects. Instead of manually writing boilerplate mapping logic, MapStruct generates efficient and type-safe mappers at compile time.</p>
<hr />
<h2 id="heading-setting-up-the-project">Setting Up the Project</h2>
<p>Add the required dependencies to your <code>pom.xml</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.mapstruct<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mapstruct<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.5.3.Final<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.mapstruct<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mapstruct-processor<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.5.3.Final<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>provided<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-jpa<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<hr />
<h2 id="heading-example-using-records-and-mapstruct-for-dtos">Example: Using Records and MapStruct for DTOs</h2>
<h3 id="heading-1-define-the-entity">1. Define the Entity</h3>
<p>Here’s the <code>Customer</code> entity:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> jakarta.persistence.Entity;
<span class="hljs-keyword">import</span> jakarta.persistence.GeneratedValue;
<span class="hljs-keyword">import</span> jakarta.persistence.GenerationType;
<span class="hljs-keyword">import</span> jakarta.persistence.Id;

<span class="hljs-meta">@Entity</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Customer</span> </span>{
    <span class="hljs-meta">@Id</span>
    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span>
    <span class="hljs-keyword">private</span> Long id;

    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> String email;

    <span class="hljs-comment">// Getters and Setters</span>
}
</code></pre>
<hr />
<h3 id="heading-2-create-a-dto-using-java-records">2. Create a DTO Using Java Records</h3>
<p>Define a <code>CustomerDTO</code>:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">CustomerDTO</span><span class="hljs-params">(Long id, String name, String email)</span> </span>{}
</code></pre>
<hr />
<h3 id="heading-3-create-a-mapstruct-mapper">3. Create a MapStruct Mapper</h3>
<p>Define an interface for mapping:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mapstruct.Mapper;
<span class="hljs-keyword">import</span> org.mapstruct.Mapping;

<span class="hljs-meta">@Mapper(componentModel = "spring")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CustomerMapper</span> </span>{
    <span class="hljs-function">CustomerDTO <span class="hljs-title">toDTO</span><span class="hljs-params">(Customer customer)</span></span>;
    <span class="hljs-function">Customer <span class="hljs-title">toEntity</span><span class="hljs-params">(CustomerDTO customerDTO)</span></span>;
}
</code></pre>
<p>Key Points:</p>
<ul>
<li><p>The <code>@Mapper</code> annotation marks this as a MapStruct mapper.</p>
</li>
<li><p>The <code>componentModel = "spring"</code> ensures the mapper is a Spring Bean.</p>
</li>
</ul>
<hr />
<h3 id="heading-4-service-layer">4. Service Layer</h3>
<p>Use the mapper in the service layer:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.stereotype.Service;

<span class="hljs-keyword">import</span> java.util.List;
<span class="hljs-keyword">import</span> java.util.stream.Collectors;

<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomerService</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CustomerRepository customerRepository;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CustomerMapper customerMapper;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CustomerService</span><span class="hljs-params">(CustomerRepository customerRepository, CustomerMapper customerMapper)</span> </span>{
        <span class="hljs-keyword">this</span>.customerRepository = customerRepository;
        <span class="hljs-keyword">this</span>.customerMapper = customerMapper;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;CustomerDTO&gt; <span class="hljs-title">getAllCustomers</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> customerRepository.findAll().stream()
            .map(customerMapper::toDTO)
            .collect(Collectors.toList());
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> CustomerDTO <span class="hljs-title">saveCustomer</span><span class="hljs-params">(CustomerDTO customerDTO)</span> </span>{
        Customer customer = customerMapper.toEntity(customerDTO);
        Customer savedCustomer = customerRepository.save(customer);
        <span class="hljs-keyword">return</span> customerMapper.toDTO(savedCustomer);
    }
}
</code></pre>
<hr />
<h3 id="heading-5-expose-an-api-endpoint">5. Expose an API Endpoint</h3>
<p>Create a controller to expose the API:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.*;

<span class="hljs-keyword">import</span> java.util.List;

<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping("/customers")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomerController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CustomerService customerService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CustomerController</span><span class="hljs-params">(CustomerService customerService)</span> </span>{
        <span class="hljs-keyword">this</span>.customerService = customerService;
    }

    <span class="hljs-meta">@GetMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;CustomerDTO&gt; <span class="hljs-title">getAllCustomers</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> customerService.getAllCustomers();
    }

    <span class="hljs-meta">@PostMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> CustomerDTO <span class="hljs-title">saveCustomer</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> CustomerDTO customerDTO)</span> </span>{
        <span class="hljs-keyword">return</span> customerService.saveCustomer(customerDTO);
    }
}
</code></pre>
<hr />
<h2 id="heading-testing-the-application">Testing the Application</h2>
<ol>
<li><p>Start the Spring Boot application.</p>
</li>
<li><p>Use Postman or curl to test the endpoints:</p>
<ul>
<li><p><strong>Get all customers</strong>:</p>
<pre><code class="lang-java">  GET /customers
</code></pre>
</li>
<li><p><strong>Save a new customer</strong>:</p>
<pre><code class="lang-java">  POST /customers
  {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Alice Johnson"</span>,
      <span class="hljs-string">"email"</span>: <span class="hljs-string">"alice@example.com"</span>
  }
</code></pre>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-advantages-of-using-records-and-mapstruct">Advantages of Using Records and MapStruct</h2>
<ol>
<li><p><strong>Immutability</strong>: Records ensure DTOs are immutable, enhancing security and predictability.</p>
</li>
<li><p><strong>Reduced Boilerplate</strong>: Records automatically generate essential methods, while MapStruct eliminates manual mapping logic.</p>
</li>
<li><p><strong>Type-Safe Mappings</strong>: MapStruct enforces type safety during compile time, reducing runtime errors.</p>
</li>
</ol>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>Combining <strong>Java Records</strong> and <strong>MapStruct</strong> is a game-changer for building clean, efficient, and maintainable Spring Boot applications. With immutable DTOs and automated object mapping, you can simplify your codebase and focus more on solving business problems.</p>
<p>Start integrating these tools in your projects today, and experience the difference they bring to your development workflow!</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Creating  SQL Queries Dynamically in Spring Boot!]]></title><description><![CDATA[Working with databases is a fundamental part of developing most applications, but sometimes, static SQL queries don’t meet the dynamic requirements of real-world applications. For example, what if you need to build a search feature that filters resul...]]></description><link>https://techwasti.com/creating-sql-queries-dynamically-in-spring-boot</link><guid isPermaLink="true">https://techwasti.com/creating-sql-queries-dynamically-in-spring-boot</guid><category><![CDATA[Spring Data Jpa]]></category><category><![CDATA[Spring Boot]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><category><![CDATA[hibernate]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Wed, 04 Dec 2024 18:30:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732843626554/0a8ecf7a-a5ef-4718-9463-6fef993e5668.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Working with databases is a fundamental part of developing most applications, but sometimes, static SQL queries don’t meet the dynamic requirements of real-world applications. For example, what if you need to build a search feature that filters results based on multiple criteria, where not all fields are mandatory? Enter <strong>Spring Data JPA Specifications</strong>, a powerful tool for creating dynamic and type-safe SQL queries.</p>
<p>In this article, we’ll explore how to use <strong>Spring Data JPA Specification</strong> to craft flexible SQL queries programmatically without getting bogged down by boilerplate code. By the end, you’ll have a clear understanding and a practical example to get started.</p>
<hr />
<h2 id="heading-why-dynamic-sql-queries">Why Dynamic SQL Queries?</h2>
<p>Static SQL queries are straightforward and often sufficient for simple use cases. However, in scenarios where:</p>
<ul>
<li><p>Query parameters are conditional (e.g., a search form where users can filter by name, age, or city, or none at all).</p>
</li>
<li><p>You need to construct queries dynamically at runtime based on user input.</p>
</li>
</ul>
<p>Static queries become rigid and unmanageable, leading to cluttered, error-prone code. Dynamic SQL queries solve this by building SQL statements programmatically.</p>
<hr />
<h2 id="heading-enter-spring-data-jpa-specification">Enter Spring Data JPA Specification</h2>
<p>Spring Data JPA’s <code>Specification</code> interface provides a clean and type-safe way to create dynamic queries. It allows you to define predicates (conditions) that combine dynamically at runtime, depending on the input.</p>
<p>Here’s the high-level approach:</p>
<ol>
<li><p>Use <code>Specification</code> to define query conditions programmatically.</p>
</li>
<li><p>Combine these conditions dynamically based on input.</p>
</li>
<li><p>Pass the <code>Specification</code> to your repository to execute the query.</p>
</li>
</ol>
<hr />
<h2 id="heading-step-by-step-guide-creating-dynamic-sql-queries">Step-by-Step Guide: Creating Dynamic SQL Queries</h2>
<p>Let’s build an example of a <strong>Customer search</strong> feature where users can filter results by <code>name</code>, <code>email</code>, or <code>city</code>.</p>
<h3 id="heading-1-setting-up-the-project">1. Setting Up the Project</h3>
<p>Ensure your Spring Boot project has the following dependencies in <code>pom.xml</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-jpa<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.h2database<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>h2<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>runtime<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<blockquote>
<p>Note: We’re using the H2 database for simplicity, but you can replace it with any database of your choice.</p>
</blockquote>
<hr />
<h3 id="heading-2-define-the-entity">2. Define the Entity</h3>
<p>Here’s our <code>Customer</code> entity:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> jakarta.persistence.Entity;
<span class="hljs-keyword">import</span> jakarta.persistence.GeneratedValue;
<span class="hljs-keyword">import</span> jakarta.persistence.GenerationType;
<span class="hljs-keyword">import</span> jakarta.persistence.Id;

<span class="hljs-meta">@Entity</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Customer</span> </span>{

    <span class="hljs-meta">@Id</span>
    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span>
    <span class="hljs-keyword">private</span> Long id;

    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> String email;
    <span class="hljs-keyword">private</span> String city;

    <span class="hljs-comment">// Getters and Setters</span>
}
</code></pre>
<hr />
<h3 id="heading-3-create-the-repository">3. Create the Repository</h3>
<p>The repository should extend <code>JpaSpecificationExecutor</code> to enable Specification-based queries:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.data.jpa.repository.JpaRepository;
<span class="hljs-keyword">import</span> org.springframework.data.jpa.repository.JpaSpecificationExecutor;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CustomerRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">Customer</span>, <span class="hljs-title">Long</span>&gt;, <span class="hljs-title">JpaSpecificationExecutor</span>&lt;<span class="hljs-title">Customer</span>&gt; </span>{
}
</code></pre>
<hr />
<h3 id="heading-4-implement-specifications">4. Implement Specifications</h3>
<p>The core logic for building dynamic queries goes into the Specification implementation:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.data.jpa.domain.Specification;
<span class="hljs-keyword">import</span> org.springframework.util.StringUtils;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomerSpecifications</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Specification&lt;Customer&gt; <span class="hljs-title">hasName</span><span class="hljs-params">(String name)</span> </span>{
        <span class="hljs-keyword">return</span> (root, query, criteriaBuilder) -&gt; 
            StringUtils.hasText(name) ? criteriaBuilder.equal(root.get(<span class="hljs-string">"name"</span>), name) : <span class="hljs-keyword">null</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Specification&lt;Customer&gt; <span class="hljs-title">hasEmail</span><span class="hljs-params">(String email)</span> </span>{
        <span class="hljs-keyword">return</span> (root, query, criteriaBuilder) -&gt; 
            StringUtils.hasText(email) ? criteriaBuilder.equal(root.get(<span class="hljs-string">"email"</span>), email) : <span class="hljs-keyword">null</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Specification&lt;Customer&gt; <span class="hljs-title">hasCity</span><span class="hljs-params">(String city)</span> </span>{
        <span class="hljs-keyword">return</span> (root, query, criteriaBuilder) -&gt; 
            StringUtils.hasText(city) ? criteriaBuilder.equal(root.get(<span class="hljs-string">"city"</span>), city) : <span class="hljs-keyword">null</span>;
    }
}
</code></pre>
<hr />
<h3 id="heading-5-combine-specifications">5. Combine Specifications</h3>
<p>Combine the conditions dynamically in the service layer based on user input:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.data.jpa.domain.Specification;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Service;

<span class="hljs-keyword">import</span> java.util.List;

<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomerService</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CustomerRepository customerRepository;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CustomerService</span><span class="hljs-params">(CustomerRepository customerRepository)</span> </span>{
        <span class="hljs-keyword">this</span>.customerRepository = customerRepository;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;Customer&gt; <span class="hljs-title">searchCustomers</span><span class="hljs-params">(String name, String email, String city)</span> </span>{
        Specification&lt;Customer&gt; spec = Specification
            .where(CustomerSpecifications.hasName(name))
            .and(CustomerSpecifications.hasEmail(email))
            .and(CustomerSpecifications.hasCity(city));

        <span class="hljs-keyword">return</span> customerRepository.findAll(spec);
    }
}
</code></pre>
<hr />
<h3 id="heading-6-expose-an-api">6. Expose an API</h3>
<p>Finally, expose this search functionality via a REST API:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestParam;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;

<span class="hljs-keyword">import</span> java.util.List;

<span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomerController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CustomerService customerService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CustomerController</span><span class="hljs-params">(CustomerService customerService)</span> </span>{
        <span class="hljs-keyword">this</span>.customerService = customerService;
    }

    <span class="hljs-meta">@GetMapping("/customers/search")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;Customer&gt; <span class="hljs-title">searchCustomers</span><span class="hljs-params">(
        <span class="hljs-meta">@RequestParam(required = false)</span> String name,
        <span class="hljs-meta">@RequestParam(required = false)</span> String email,
        <span class="hljs-meta">@RequestParam(required = false)</span> String city)</span> </span>{
        <span class="hljs-keyword">return</span> customerService.searchCustomers(name, email, city);
    }
}
</code></pre>
<hr />
<h3 id="heading-testing-the-api">Testing the API</h3>
<ol>
<li><p>Start the application and access the H2 Console at <a target="_blank" href="http://localhost:8080/h2-console"><code>http://localhost:8080/h2-console</code></a>.</p>
</li>
<li><p>Insert some test data:</p>
</li>
</ol>
<pre><code class="lang-java"><span class="hljs-function">INSERT INTO <span class="hljs-title">CUSTOMER</span> <span class="hljs-params">(NAME, EMAIL, CITY)</span> <span class="hljs-title">VALUES</span> 
<span class="hljs-params">(<span class="hljs-string">'John Doe'</span>, <span class="hljs-string">'john@example.com'</span>, <span class="hljs-string">'New York'</span>)</span>,
<span class="hljs-params">(<span class="hljs-string">'Jane Smith'</span>, <span class="hljs-string">'jane@example.com'</span>, <span class="hljs-string">'Los Angeles'</span>)</span>,
<span class="hljs-params">(<span class="hljs-string">'Alice Johnson'</span>, <span class="hljs-string">'alice@example.com'</span>, <span class="hljs-string">'New York'</span>)</span></span>;
</code></pre>
<ol start="3">
<li><p>Test the API using Postman or curl:</p>
<ul>
<li><p>Filter by <code>city</code>:</p>
<pre><code class="lang-java">  GET /customers/search?city=New York
</code></pre>
</li>
<li><p>Filter by <code>name</code> and <code>email</code>:</p>
<pre><code class="lang-java">  GET /customers/search?name=John Doe&amp;email=john<span class="hljs-meta">@example</span>.com
</code></pre>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ul>
<li><p><strong>Simplicity</strong>: Spring Data JPA Specification eliminates the need for writing raw SQL queries.</p>
</li>
<li><p><strong>Flexibility</strong>: Conditions are dynamically combined, making it ideal for scenarios with optional filters.</p>
</li>
<li><p><strong>Scalability</strong>: Add more fields or filters easily by creating new Specification methods.</p>
</li>
</ul>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>Dynamic SQL queries can greatly simplify the handling of complex and flexible database requirements. Using Spring Data JPA Specifications, we crafted dynamic and type-safe queries programmatically. This approach not only reduces boilerplate code but also makes your application more adaptable to future changes.</p>
<p>Experiment with this example in your projects and see how it transforms your database interactions!</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://techwasti.com/Link"><strong>https://medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>https://www.youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>https://techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>https://techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[End-to-End Guide to Spring Data REST with PostgreSQL!]]></title><description><![CDATA[Spring Data REST simplifies the creation of RESTful APIs for Spring Data repositories. It eliminates boilerplate code and automatically exposes repository methods as REST endpoints. This guide demonstrates how to create a Spring Data REST application...]]></description><link>https://techwasti.com/end-to-end-guide-to-spring-data-rest-with-postgresql</link><guid isPermaLink="true">https://techwasti.com/end-to-end-guide-to-spring-data-rest-with-postgresql</guid><category><![CDATA[Springboot]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Spring Data Jpa]]></category><category><![CDATA[Spring framework]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[programming]]></category><category><![CDATA[interview]]></category><dc:creator><![CDATA[Maheshwar Ligade]]></dc:creator><pubDate>Tue, 03 Dec 2024 18:30:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732000438384/830cf650-1c8a-470e-a9ed-6bbb304129f0.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Spring Data REST simplifies the creation of RESTful APIs for Spring Data repositories. It eliminates boilerplate code and automatically exposes repository methods as REST endpoints. This guide demonstrates how to create a Spring Data REST application with PostgreSQL.</p>
<hr />
<h2 id="heading-project-setup"><strong>Project Setup</strong></h2>
<h3 id="heading-dependencies"><strong>Dependencies</strong></h3>
<p>Add the following dependencies in your <code>pom.xml</code> for a Maven project:</p>
<pre><code class="lang-java">&lt;dependencies&gt;
    &lt;!-- Spring Boot Starter Web --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- Spring Boot Starter Data JPA --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- Spring Data REST --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-rest&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- PostgreSQL Driver --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.postgresql&lt;/groupId&gt;
        &lt;artifactId&gt;postgresql&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<hr />
<h3 id="heading-application-properties"><strong>Application Properties</strong></h3>
<p>Configure the PostgreSQL database in <a target="_blank" href="http://application.properties"><code>application.properties</code></a> or <code>application.yml</code>:</p>
<pre><code class="lang-java"># PostgreSQL Configuration
spring.datasource.url=jdbc:postgresql:<span class="hljs-comment">//localhost:5432/mydatabase</span>
spring.datasource.username=myusername
spring.datasource.password=mypassword
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
</code></pre>
<hr />
<h3 id="heading-database-schema"><strong>Database Schema</strong></h3>
<p>Create a PostgreSQL database named <code>mydatabase</code>:</p>
<pre><code class="lang-java">CREATE DATABASE mydatabase;
</code></pre>
<hr />
<h2 id="heading-creating-the-application"><strong>Creating the Application</strong></h2>
<h3 id="heading-1-define-the-entity"><strong>1. Define the Entity</strong></h3>
<p>Create an entity class <code>Employee</code> representing the database table:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.springdatarest.entity;

<span class="hljs-keyword">import</span> jakarta.persistence.Entity;
<span class="hljs-keyword">import</span> jakarta.persistence.GeneratedValue;
<span class="hljs-keyword">import</span> jakarta.persistence.GenerationType;
<span class="hljs-keyword">import</span> jakarta.persistence.Id;

<span class="hljs-meta">@Entity</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Employee</span> </span>{

    <span class="hljs-meta">@Id</span>
    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span>
    <span class="hljs-keyword">private</span> Long id;
    <span class="hljs-keyword">private</span> String firstName;
    <span class="hljs-keyword">private</span> String lastName;
    <span class="hljs-keyword">private</span> String role;

    <span class="hljs-comment">// Getters and Setters</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getId</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> id;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setId</span><span class="hljs-params">(Long id)</span> </span>{
        <span class="hljs-keyword">this</span>.id = id;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getFirstName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> firstName;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setFirstName</span><span class="hljs-params">(String firstName)</span> </span>{
        <span class="hljs-keyword">this</span>.firstName = firstName;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getLastName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> lastName;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setLastName</span><span class="hljs-params">(String lastName)</span> </span>{
        <span class="hljs-keyword">this</span>.lastName = lastName;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getRole</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> role;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setRole</span><span class="hljs-params">(String role)</span> </span>{
        <span class="hljs-keyword">this</span>.role = role;
    }
}
</code></pre>
<hr />
<h3 id="heading-2-create-the-repository"><strong>2. Create the Repository</strong></h3>
<p>Spring Data REST will expose this repository as REST endpoints automatically.</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.springdatarest.repository;

<span class="hljs-keyword">import</span> com.example.springdatarest.entity.Employee;
<span class="hljs-keyword">import</span> org.springframework.data.repository.CrudRepository;
<span class="hljs-keyword">import</span> org.springframework.data.rest.core.annotation.RepositoryRestResource;

<span class="hljs-meta">@RepositoryRestResource</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">EmployeeRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">CrudRepository</span>&lt;<span class="hljs-title">Employee</span>, <span class="hljs-title">Long</span>&gt; </span>{
}
</code></pre>
<hr />
<h3 id="heading-3-bootstrapping-the-application"><strong>3. Bootstrapping the Application</strong></h3>
<p>Create the main application class:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.springdatarest;

<span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;
<span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;

<span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SpringDataRestApplication</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        SpringApplication.run(SpringDataRestApplication.class, args);
    }
}
</code></pre>
<hr />
<h2 id="heading-testing-the-api"><strong>Testing the API</strong></h2>
<h3 id="heading-default-endpoints"><strong>Default Endpoints</strong></h3>
<ul>
<li><p><code>GET /employees</code>: Retrieve all employees.</p>
</li>
<li><p><code>POST /employees</code>: Add a new employee.</p>
</li>
<li><p><code>GET /employees/{id}</code>: Retrieve a specific employee by ID.</p>
</li>
<li><p><code>PUT /employees/{id}</code>: Update an existing employee.</p>
</li>
<li><p><code>DELETE /employees/{id}</code>: Delete an employee.</p>
</li>
</ul>
<hr />
<h3 id="heading-sample-payload"><strong>Sample Payload</strong></h3>
<h4 id="heading-post-request-to-employees"><strong>POST Request to</strong> <code>/employees</code></h4>
<pre><code class="lang-java">{
  <span class="hljs-string">"firstName"</span>: <span class="hljs-string">"John"</span>,
  <span class="hljs-string">"lastName"</span>: <span class="hljs-string">"Doe"</span>,
  <span class="hljs-string">"role"</span>: <span class="hljs-string">"Developer"</span>
}
</code></pre>
<h4 id="heading-response"><strong>Response</strong></h4>
<pre><code class="lang-java">{
  <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-string">"firstName"</span>: <span class="hljs-string">"John"</span>,
  <span class="hljs-string">"lastName"</span>: <span class="hljs-string">"Doe"</span>,
  <span class="hljs-string">"role"</span>: <span class="hljs-string">"Developer"</span>
}
</code></pre>
<h4 id="heading-get-request-to-employees1"><strong>GET Request to</strong> <code>/employees/1</code></h4>
<pre><code class="lang-java">{
  <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-string">"firstName"</span>: <span class="hljs-string">"John"</span>,
  <span class="hljs-string">"lastName"</span>: <span class="hljs-string">"Doe"</span>,
  <span class="hljs-string">"role"</span>: <span class="hljs-string">"Developer"</span>
}
</code></pre>
<hr />
<h2 id="heading-customizing-the-rest-endpoints"><strong>Customizing the REST Endpoints</strong></h2>
<h3 id="heading-changing-endpoint-path"><strong>Changing Endpoint Path</strong></h3>
<p>Use the <code>@RepositoryRestResource</code> annotation to customize endpoint paths:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RepositoryRestResource(path = "staff")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">EmployeeRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">CrudRepository</span>&lt;<span class="hljs-title">Employee</span>, <span class="hljs-title">Long</span>&gt; </span>{
}
</code></pre>
<p>Now, access employees at <code>/staff</code> instead of <code>/employees</code>.</p>
<hr />
<h3 id="heading-projection-for-custom-views"><strong>Projection for Custom Views</strong></h3>
<p>You can define projections to customize the fields exposed by the API:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.springdatarest.projection;

<span class="hljs-keyword">import</span> com.example.springdatarest.entity.Employee;
<span class="hljs-keyword">import</span> org.springframework.data.rest.core.config.Projection;

<span class="hljs-meta">@Projection(name = "employeeSummary", types = { Employee.class })</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">EmployeeSummary</span> </span>{
    <span class="hljs-function">String <span class="hljs-title">getFirstName</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function">String <span class="hljs-title">getLastName</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<h4 id="heading-usage"><strong>Usage</strong></h4>
<ul>
<li>GET <code>/employees?projection=employeeSummary</code></li>
</ul>
<hr />
<h2 id="heading-error-handling"><strong>Error Handling</strong></h2>
<p>Spring Data REST provides default error handling. Customize it using <code>@ControllerAdvice</code> for global exceptions:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.example.springdatarest.exception;

<span class="hljs-keyword">import</span> org.springframework.http.HttpStatus;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.ControllerAdvice;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.ExceptionHandler;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.ResponseStatus;

<span class="hljs-meta">@ControllerAdvice</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> </span>{

    <span class="hljs-meta">@ExceptionHandler(RuntimeException.class)</span>
    <span class="hljs-meta">@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">handleRuntimeException</span><span class="hljs-params">(RuntimeException ex)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"An error occurred: "</span> + ex.getMessage();
    }
}
</code></pre>
<hr />
<h2 id="heading-testing-with-postman"><strong>Testing with Postman</strong></h2>
<ol>
<li><p>Start your application using <code>SpringDataRestApplication</code>.</p>
</li>
<li><p>Use <strong>Postman</strong> or <strong>curl</strong> to interact with the API:</p>
<ul>
<li><code>GET</code>, <code>POST</code>, <code>PUT</code>, and <code>DELETE</code> requests to <code>/employees</code>.</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>With Spring Data REST, you can quickly expose repositories as RESTful APIs, reducing boilerplate and development time. By integrating with PostgreSQL, this guide demonstrates an end-to-end solution for managing database entities efficiently. Customize your endpoints and data exposure as needed for more control over your API.</p>
<p><strong>More such articles:</strong></p>
<p><a target="_blank" href="https://medium.com/techwasti"><strong>medium.com/techwasti</strong></a></p>
<p><a target="_blank" href="https://www.youtube.com/@maheshwarligade"><strong>youtube.com/@maheshwarligade</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/spring-boot-tutorials"><strong>techwasti.com/series/spring-boot-tutorials</strong></a></p>
<p><a target="_blank" href="https://techwasti.com/series/go-language"><strong>techwasti.com/series/go-language</strong></a></p>
]]></content:encoded></item></channel></rss>