<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Engineering at Runa - Medium]]></title>
        <description><![CDATA[Runa is pioneering a first-of-its-kind payment rail capable of sending and accepting digital value at scale. With Runa, companies can pay the people they serve in any form of digital value, anywhere, instantly. - Medium]]></description>
        <link>https://medium.com/runa-engineering?source=rss----acc23e73e72f---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Engineering at Runa - Medium</title>
            <link>https://medium.com/runa-engineering?source=rss----acc23e73e72f---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 11 Nov 2026 22:19:55 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/runa-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Stop Breaking Production: A Step Function Rollout Strategy That Works]]></title>
            <link>https://medium.com/runa-engineering/stop-breaking-production-a-step-function-rollout-strategy-that-works-d1f693e5be05?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/d1f693e5be05</guid>
            <dc:creator><![CDATA[Anita Ernszt]]></dc:creator>
            <pubDate>Wed, 01 Apr 2026 11:45:45 GMT</pubDate>
            <atom:updated>2026-04-01T11:45:44.343Z</atom:updated>
            <content:encoded><![CDATA[<p>How confident are you that your next change won’t blow up production?</p><p>In traditional server-based systems, gradual rollouts and canary deployments are standard practice. In serverless systems, however, we too often treat infrastructure as magic black boxes rather than critical, complex systems that deserve the same rigor. Even with extensive pre-production testing, this leaves us exposed to unexpected outages.</p><p>Gradual rollouts are not a “nice-to-have” — they’re essential, even for serverless.</p><h3>Why gradual rollouts matter to us</h3><p>Runa embarked on its serverless journey nearly 3 years ago, adopting serverless technologies to power our next-generation global payout platform. The platform needs to be fast, but reliability is non-negotiable.</p><p>At Runa, this problem is not theoretical. We use Step Functions extensively across our platform, and they sit on the path of many of our most important production workflows. That scale is what made rollout safety impossible to ignore. Engineers ship changes several times a day. Every release introduces risk, and without guardrails, that risk quickly compounds. To maintain high deployment velocity without compromising availability, we needed a deployment mechanism that ensured:</p><ul><li>Smooth end-user experience during releases</li><li>Real-time monitoring and automated rollback capabilities</li></ul><p>AWS already gives Lambda users a familiar deployment model via CodeDeploy, with built-in traffic shifting, and automated rollback. We wanted the equivalent operational model for Step Functions in the Terraform-driven environment we actually run. That is what led us to build Tweety.</p><h3>Defining Requirements</h3><p>Before designing the rollout mechanism, we aligned on a small set of baseline requirements. These were not aspirational goals. They were constraints driven by how we build and operate production systems:</p><ul><li><strong>Terraform Compatibility<br></strong>Our infrastructure is managed via Terraform. The rollout solution had to integrate seamlessly, leveraging the tools our engineers already use.</li><li><strong>Automation and Guardrails<br></strong>Rollout and rollback processes had to be fully automated with built-in safeguards to prevent accidental missteps.</li><li><strong>Simplicity for Engineers<br></strong>The underlying implementation could be complex, but interacting with it should not be. From an engineer’s perspective, deploying with gradual rollouts needed to feel no different from a standard release.</li></ul><h3>Planning for a rollout</h3><p>Before we could even start thinking about gradual releases, we needed two critical capabilities:</p><ul><li>The ability to version your changes</li><li>The ability to balance traffic between the versions</li></ul><p>A gradual rollout only works if we can precisely control which version of a Step Function is executed at any given time. AWS Step Functions already support versioning (publishing a version creates an immutable snapshot of the state machine definition). Using aliases, we can then control which version is invoked and gradually shift traffic between versions.</p><p>In AWS, invoking a specific version is achieved using qualified ARNs, which explicitly reference either a published version or an alias.</p><p>For example:</p><ul><li><strong>Unqualified ARN</strong> — always points to the latest version:</li></ul><pre>arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessor</pre><ul><li><strong>Version Qualified ARN</strong> — explicitly points to a published version:</li></ul><pre>arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessor:3</pre><ul><li><strong>Alias Qualified ARN</strong> — points to an alias that can route traffic between versions:</li></ul><pre>arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessor:prod</pre><p>In order to ensure stability during the rollout we must use qualified ARNs across our infrastructure.</p><h3>Only shift traffic at the entry point</h3><p>Because Step Functions execute downstream services as part of a single workflow, traffic shifting can only happen at the entry point, which is the alias of the top-level state machine being invoked. Any child resources, such as nested Step Functions or Lambdas, must be version pinned.</p><p>If we attempt to shift traffic for downstream resources independently, we risk creating version mismatches when the contract between versions changes. For example, a new state machine may invoke an older Lambda with an incompatible input, or an older state machine may call a newer Lambda expecting fields that are no longer present. These mismatches can lead to runtime errors or partial outages that are hard to detect during rollout.</p><p>The diagram below shows an example of an incorrect rollout, where traffic is shifted independently across state machines and downstream Lambdas:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-TcPwRYF38YvcvRbMwC9QA.png" /></figure><h3>Version pin downstream dependencies</h3><p>To ensure that changes to child resources are rolled out gradually and safely, we made version pinning mandatory for all downstream resources. This prevents changes from reaching production immediately and ensures that each Step Function version executes against a consistent set of dependencies.</p><p>Version pinning also plays a key role in driving the gradual rollout process as new versions of downstream resources are introduced and referenced by a new state machine version.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4LxlyIgLOICuZK1UFj8FoQ.png" /></figure><h3>IAM resources are not versioned</h3><p>This is easy to overlook during rollouts and has caused real production failures for us in the past.</p><p>While Step Functions can be versioned and aliased, IAM roles and policies have no built-in versioning. All versions of a Step Function share the same IAM configuration.</p><p>This has an important implication. Any IAM change must remain fully backward compatible with <strong>all</strong> Step Function versions that are still receiving traffic. Removing permissions, even if they are no longer required by the latest version, can cause older executions to fail with AccessDenied errors during a rollout.</p><p>Once we were able to version workflows and shift traffic safely, we formalised 3 non-negotiable rules:</p><ol><li>Pin all downstream resources</li><li>Shift traffic only at the entry point</li><li>Keep IAM changes backward compatible</li></ol><p>Violating any of these rules risks breaking production in the middle of a rollout.</p><h3>Meet Tweety</h3><p>To orchestrate the release process, we built Tweety, our in-house release manager implemented as a Step Function. The name is inspired by the Looney Tunes canary, reflecting Tweety’s role as an early warning system during deployments.</p><p>The idea behind Tweety is deliberately simple. If you have used Lambda canary deployments via CodeDeploy before, the model will feel familiar: publish an immutable version, shift traffic gradually, monitor health, and roll back automatically when signals degrade.</p><p>Tweety brings that operating model to Step Functions, packaged in a way that fits our Terraform workflows, our dependency-pinning rules, and the realities of rolling out orchestration logic safely.</p><p>It handles rollout between versions, monitors health checks, and automatically rolls back if alarms are triggered:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DEMmUX88vO8jOAMrS7J_4A.png" /></figure><p>To kick off the gradual rollout process, we invoke Tweety whenever a Step Function version changes. The invocation is triggered from Terraform using the AWS CLI, with user-defined inputs such as traffic shift percentage, rollout interval, and monitoring thresholds:</p><pre>resource &quot;null_resource&quot; &quot;run_gradual_rollout&quot; {<br>  triggers = {<br>    sfn_version = var.version_arn<br>  }</pre><pre>provisioner &quot;local-exec&quot; {<br>    # Only trigger deployment if the state machine version is not equal to 1<br>    command = &lt;&lt;EOT<br>      if [ ${var.version_arn} != ${local.current_version_arn} ]; then <br>        aws stepfunctions start-execution \\<br>          --state-machine-arn ${var.gradual_rollout_stepfunction} \\<br>          --input &#39;${jsonencode(local.rollout_payload)}&#39;<br>      fi<br>    EOT<br>  }<br>}</pre><p>Once the rollout is initiated, Tweety runs two jobs in parallel:</p><ul><li><strong>Traffic management</strong> This job determines the next traffic weight and updates the Step Function alias accordingly.</li><li><strong>Health checks</strong> In parallel, the Health check job continuously evaluates system metrics against team-defined thresholds. If any degradation is detected, it stops the rollout, initiates a rollback, and alerts the team that the release has failed.</li></ul><p>Building our own gradual rollout process allowed us to fully customise it to our needs, from Slack notifications to support for non-standard deployment strategies.</p><h3>DevX as a functional requirement</h3><p>Ease of adoption was a first-class concern when designing the rollout solution. Gradual rollouts had to be simple to adopt and should not require engineers to understand the internal workings of the rollout engine. To achieve this, we wrapped the required resources in a Terraform module that hides the complexity and manages the gradual rollout process end to end.</p><p>From an engineer’s perspective, deploying with gradual rollouts feels no different from any other change, except that it is safer. Engineers provide a small set of inputs to a Terraform module, and the rest is handled automatically: versioning, alias shifts, monitoring, rollback, and Slack updates.</p><p>The visual execution graphs and built-in logging provided by Step Functions also help engineers better understand the deployment process and simplify troubleshooting when things go wrong.</p><h3>Closing thoughts</h3><p>Once the gradual rollout mechanism was in place, it was quickly adopted across our engineering teams. A key enabler was the set of custom Terraform modules we built, which made safe rollouts easy to apply and operate consistently.</p><p>By adding safety guardrails to Step Functions, we enabled engineers to move fast and deploy with confidence, even during peak season, without putting production stability at risk.</p><p>While AWS provides powerful primitives such as Step Function versions and aliases, building a safe rollout workflow still requires stitching those pieces together with operational guardrails. Tweety is our attempt to codify those best practices into a reusable tool.</p><p>We are now open-sourcing the Terraform module behind Tweety so other teams can adopt and adapt the same approach in their own AWS environments.</p><p>To get started, you can find our open-source module here: <a href="https://registry.terraform.io/modules/wegift/tweety-sfn/aws/latest">https://registry.terraform.io/modules/wegift/tweety-sfn/aws/latest</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1f693e5be05" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/stop-breaking-production-a-step-function-rollout-strategy-that-works-d1f693e5be05">Stop Breaking Production: A Step Function Rollout Strategy That Works</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Pains of the Cloud: Deprecated Services and why Lambdas are King]]></title>
            <link>https://medium.com/runa-engineering/the-pains-of-the-cloud-deprecated-services-and-why-lambdas-are-king-e1c16a483059?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/e1c16a483059</guid>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <dc:creator><![CDATA[moni]]></dc:creator>
            <pubDate>Wed, 04 Feb 2026 14:56:47 GMT</pubDate>
            <atom:updated>2026-02-04T14:56:25.127Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/822/1*TnBlYpAabuVo4-6Z1k32-Q.png" /></figure><p>Just like for a lot of software businesses out there, most of Runa’s services are based on a Cloud-based microservices design with AWS being our exclusive cloud provider. To build systems capable of processing hundreds of thousands of events in seconds in a reliable manner, we have additionally adopted the approach to favour serverless AWS services in our architecture.</p><p>In a serverless model, developers can build and deploy applications without having to manage the underlying infrastructure. The cloud provider will dynamically allocate resources based on usage, allowing our teams to develop and deploy faster while focusing on business logic.</p><p>AWS offers various serverless services across different domains, including API management (API Gateway), compute (Lambda, App Runner), event-driven integration (EventBridge, Step Functions, SQS, and SNS), and data storage (S3, DynamoDB, and Aurora Serverless).</p><p>As part of the Billing team, a sub-team of engineering, responsible for all things related to financial reconciliation and invoicing of customers and brands we work with, I have worked extensively with most of these over the past months at Runa.</p><p>Although on paper, these services might seem like a perfect solution to most problems, and they generally do provide a great developer experience, it’s worth noting that it hasn’t always been smooth sailing. There are trade-offs and pitfalls to navigate along the way.</p><h3>The Common Challenges With Being Distributed Over Many Cloud Services</h3><p>It is well-known that microservices increase the operational overhead and complexity of the system, making it more challenging for developers to follow and work with. This is no different in the Cloud, although AWS’s offering of supporting tools, combined with our preference for serverless, makes it significantly easier.</p><p>One of the main challenges with having the system components broken down into various Cloud services is ensuring adequate observability and monitoring — tracing a request end-to-end across a Step Function, which might invoke a Lambda, which in turn interacts with other services… although some supporting AWS services such as X-Ray and CloudWatch assist with logging and tracing these also have to be integrated with our internal observability framework and third-party tools like Datadog (and how we go about it deserves its own article 🙂) to achieve a reliable and comprehensive alerting and monitoring system as well as more manageable debugging of issues.</p><h3>Some Stumbling Blocks Associated With Specific Services</h3><p>Over the years, we have also encountered some limitations and complications linked to specific AWS services. For example, some commonly occurring issues with “phantom” requests in API Gateway and how we addressed them were described by my colleague in this <a href="https://medium.com/runa-engineering/fast-reliable-serverless-from-50-minutes-to-50-seconds-a-year-of-downtime-on-aws-88bd5b1b330b"><strong>article.</strong></a></p><p>Last year, we were forced to migrate our Quantum Ledger Database (QLDB) to another service, as AWS announced its deprecation, with the shutdown scheduled for July 31, 2025. As a managed ledger database service, it offered a cryptographically verifiable transaction log, making it a perfect choice where the immutability of records and audit functionality were necessary. Apart from serverless support, QLDB offered integration with our current infrastructure (Step Functions, API Gateway) and fit our data model for the payouts system, in which a record is stored in a document format with support for nested objects and document history out of the box. To obtain a permanent, immutable history of all record changes for audit purposes, which QLDB offered with a different database service, those transaction logs can still be tracked and permanently stored, but only with the support of other services, for example, CloudTrail or S3.</p><h3>Serverless Favorites</h3><p>At Runa, one of the main domains in our infrastructure where the serverless services’ dominance is particularly visible is computing and workflow orchestration. Lambdas and Step Functions form the backbone of our infrastructure and underpin many of our services. They are used to power everything from real-time API calls to scheduled background processes and are widely adopted by all our teams. Lambdas let us run lightweight, event-driven workloads on demand, while Step Functions orchestrate more complex workflows across multiple services. Since each service deserves a closer look, this article will focus solely on AWS Lambda.</p><p>Lambdas are a popular choice when you want to run code without having to worry about how it will be run — AWS will provision and manage servers for you, and even automatically scale based on traffic demand. Additionally, you only pay for the amount of time the code is running (the charges are based on the number of requests and the time it takes to run them).</p><p>Lambda natively connects to AWS event sources (SNS, SQS, API Gateway, EventBridge, etc.), making it an ideal component for irregular or bursty workloads. These services act as managed ingress points that absorb traffic spikes, buffer events, and invoke Lambda functions only when work is available. As a result, there is no need to provision or scale compute capacity in advance: Lambda is invoked reactively in response to events, and automatically scales concurrency to match the incoming workload. Some of our workloads run on an infrequent schedule and may consume fluctuating amounts of resources — for example, our invoicing cycle runs only once a month on the first day. During the billing cycle, a large amount of resources will be consumed due to a high volume of data to be processed, but Lambda functions will scale automatically to the workload, which makes it a perfect choice since we can run these processes without having idle infrastructure outside of that time.</p><p>Lambda’s features help us to create a system that is not only more maintainable but also resilient — the function automatically retries failed asynchronous invocations and provides high availability out of the box. When integrated with durable event buses such as SNS and SQS, Lambdas can offer a decoupled and fault-tolerant workflow — for example, our invoicing system contains several Lambdas which consume and produce events that get processed along the pipeline with queues and topics keeping the services decoupled. Each Lambda is responsible for its own logic, as AWS best practices recommend writing single-purpose functions. This separation offers additional resilience under unpredictable loads; for example, a queue can act as a buffer if the downstream Lambda is experiencing a temporary slowness in processing the events.</p><h3>Things to Consider When Working With Lambdas</h3><p>It is worth mentioning that there are also some well-known drawbacks to using Lambdas. These include vendor lock-in with heavy reliance on AWS-managed services (something which is a lesser concern for us, but can be tackled with hexagonal architectures and standard runtimes) or execution and resource limits of the Lambdas, which need to be considered when developing a service. AWS limits the executions to 15 minutes per invocation, which makes it unsuitable for long-running jobs unless they are broken down into subtasks and split between multiple Lambdas or a Step Function coordinating each task separately. Additionally, as there are resource caps on Lambda memory and ephemeral disk storage, they might not be a suitable option for resource-intensive tasks.</p><p>Another well-known problem with Lambdas is what’s known as a cold start, which occurs when a function hasn’t been used recently and the first invocation requires AWS to spin up a new execution environment. This adds additional latency to the first request and can be particularly noticeable for infrequently used, latency-sensitive paths. In practice, however, this impact can be reduced significantly. For critical APIs, provisioned concurrency allows us to keep execution environments warm and predictable. Provisioned concurrency means that (if provisioned to the right scale) we always have a lambda that is warm and ready to compute. It is great for:</p><p>- When we have a critical but low traffic endpoint that requires high performance<br>- When we have a lambda that’s package size results in significant startup time</p><p>We can also mitigate cold starts by keeping deployment packages small, avoiding unnecessary dependencies, choosing lightweight runtimes for hot paths, and deferring expensive initialisation logic until it is actually needed. For workloads where some startup latency is acceptable, like in our invoicing lambdas, cold starts become largely irrelevant, allowing us to benefit from Lambda’s scaling and cost model without compromising user experience.</p><p>Our biggest concern in the example of the invoicing workflow was implementing an acceptable and reliable observability of that system. Having multiple distributed Lambdas, which additionally interact with other services, adds to the complexity and makes it harder to monitor and debug. We were able to overcome these challenges using centralised monitoring, which integrates AWS services such as X-ray, third-party tools (Datadog) and in-house solutions.</p><h3>Final Thoughts</h3><p>While AWS services have enabled us to develop and scale quickly, our journey has also revealed the challenges that come with depending on a constantly evolving cloud ecosystem. From troubleshooting service-specific issues to navigating the forced deprecation of QLDB, we’ve repeatedly had to adapt our systems and revisit design choices. Operating across many cloud services amplifies this complexity, making flexibility and resilience in system design essential to absorb change without disrupting the platform.</p><p>Despite these challenges, this approach has been the right one for us: managed services have allowed our teams to focus on product and business logic rather than infrastructure concerns, while still supporting rapid growth and highly variable workloads. By deliberately designing for loose coupling and clear boundaries between services, we’ve been able to evolve or replace underlying components when necessary without slowing down delivery.</p><p>These experiences highlight an important reality of building serverless platforms at scale: the advantages of managed services come with trade-offs, including limited control and potential service discontinuity. For us, the key lesson has been to build with flexibility in mind — designing integrations and data flows in a way that allows us to pivot when services change, without disrupting product delivery.</p><p>Within that context, AWS Lambda remains the engineering team’s compute favourite. It enables us to move faster while absorbing a significant amount of operational burden through automatic scaling, per-invocation billing, native retries for asynchronous invocations, and easy integration with AWS event sources. This makes it particularly well-suited for event-driven pipelines that must handle large, unpredictable bursts — such as month-end invoice processing — without provisioning or maintaining idle infrastructure. Lambdas are not a silver bullet, but for us, their limitations are well understood and manageable when paired with deliberate architectural and operational practices.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1c16a483059" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/the-pains-of-the-cloud-deprecated-services-and-why-lambdas-are-king-e1c16a483059">The Pains of the Cloud: Deprecated Services and why Lambdas are King</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Terraforming Datadog Workflows]]></title>
            <link>https://medium.com/runa-engineering/terraforming-datadog-workflows-0673abefe87b?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/0673abefe87b</guid>
            <category><![CDATA[techops]]></category>
            <category><![CDATA[datadog]]></category>
            <category><![CDATA[workflow-automation]]></category>
            <category><![CDATA[observability]]></category>
            <dc:creator><![CDATA[Kitan]]></dc:creator>
            <pubDate>Mon, 12 Jan 2026 16:46:06 GMT</pubDate>
            <atom:updated>2026-01-12T16:46:05.300Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/822/1*h-nVOx3j96lueOrwSLt0Ng.png" /></figure><p>Terraform recently added the datadog_workflow_automation resource to its <a href="https://registry.terraform.io/providers/DataDog/datadog/3.58.0/docs">Datadog registry</a>. This new resource enables teams to provision and manage Datadog workflows using Terraform, giving teams the ease and flexibility of infrastructure-as-code for observability automation.</p><p>At Runa, we have found workflows to be especially useful in <strong>operations management, alert</strong> <strong>optimization,</strong> and <strong>incident flow automation</strong>. However, managing these workflows solely via the Datadog UI quickly becomes difficult. Changes are hard to track, reviews are not centralized, and small edits can easily get lost. Combined with the challenge of maintaining workflows that serve multiple teams with different priorities, these gaps become painful and error-prone.</p><p>This why I have spent the past few months migrating two of our existing operations management workflows to Terraform. In this post, I will take you through the <strong>key technical challenges</strong>, the <strong>solutions,</strong> and the <strong>pitfalls</strong> to avoid when adopting Terraform for Datadog workflows.</p><h3>Key Technical Challenges and Solutions</h3><h4>Complex JSON Structure Management</h4><p>The most significant challenge was managing the complex JSON structure required for the spec_json parameter. Datadog workflows have intricate step definitions with nested parameters, outbound edges, and display configurations.</p><p><strong>Solution</strong>: Export the workflow from the Datadog console as Terraform code.</p><p>The Datadog console provides an “Export” option for existing workflows that outputs a complete Terraform representation of the workflow. This gives you the exact JSON format that Datadog expects, minimizing trial-and-error. From there, you can refactor the spec_json using jsonencode() and Terraform locals to make it more modular and maintainable.</p><p><strong>Steps to export</strong>:</p><ol><li>Go to your workflow in the Datadog console.</li><li>Click on the Export option in the menu bar.</li><li>Select the Terraform option.</li><li>Copy the generated spec_json content.</li></ol><h4>Dynamic Step References and Dependencies</h4><p>One of the trickiest aspects was handling dynamic references between steps that are conditionally included. For example, one workflow includes a call to another workflow only when a specific criterion is met, which means that the outbound edges (the “links” between steps) must reference steps that may or may not exist.</p><p><strong>Solution</strong>: Use Terraform locals to define conditional outbound edge maps. This approach allows you to model branching logic without duplicating JSON structures.</p><pre>locals {<br>  check_monitor_outbound_edges = var.enable_notifications ? [<br>    {<br>      &quot;nextStepName&quot; : &quot;SendSlackNotification&quot;,<br>      &quot;branchName&quot; : &quot;true&quot;<br>    }<br>  ] : []<br>}</pre><pre>{<br>          &quot;name&quot; : &quot;CheckMonitorStatus&quot;,<br>          &quot;actionId&quot; : &quot;com.datadoghq.core.if&quot;,<br>          &quot;parameters&quot; : [ ... ],<br>          &quot;outboundEdges&quot; : local.check_monitor_outbound_edges,<br>        }</pre><h4>AWS Services Integration and Connection Management</h4><p>Both workflows integrate with multiple AWS services, which require authentication. On the Datadog console, AWS connection is easily managed by creating an appropriate connection for each environment. However, Terraform is environment agnostic, so you must supply the AWS connection for the correct environment being deployed.</p><p><strong>Solution</strong>: Maintain a mapping of pre-existing environment-specific AWS connections:</p><pre><br>locals {<br>  aws_connection_map = {<br>    &quot;sandbox&quot;    = { connection_id : &quot;12345&quot;, label : &quot;SAMPLE_CONNECTION_1&quot; }<br>    &quot;dev&quot;    = { connection_id : &quot;67890&quot;, label : &quot;SAMPLE_CONNECTION_2&quot; }<br>  }<br>}</pre><h4>Dynamic workflow naming</h4><p>Not a challenge, but worth mentioning to avoid all provisioned workflows having the same name.</p><p><strong>Solution</strong>: Use Terraform variables to parameterize workflow names</p><pre>resource &quot;datadog_workflow_automation&quot; &quot;my_workflow&quot; {<br>  name        = &quot;My-workflow-${var.team_name}-${var.env}&quot;<br>}</pre><h4>JavaScript Code in Workflow Steps</h4><p>Many workflow steps include JavaScript code for data transformation. Managing this code within Terraform strings can quickly become messy and hard to read.</p><p><strong>Best Practices</strong>:</p><ul><li>Write and test your code directly in the Datadog workflow <strong>JavaScript step editor</strong> or any IDE.</li><li>Maintain proper indentation and commenting.</li><li>Export the working code as JSON from Datadog or IDE once validated.</li></ul><pre>{<br>  &quot;name&quot; : &quot;ParseOutput&quot;,<br>  &quot;actionId&quot; : &quot;com.datadoghq.datatransformation.func&quot;,<br>  &quot;parameters&quot; : [<br>    {<br>      &quot;name&quot; : &quot;script&quot;,<br>      &quot;value&quot; : &quot;// parse JSON string \nlet parsedFaults = $.Steps.DescribeOutput;\nlet trigger_monitor_url = $.Source.monitor.url;\n\n ... // rest of logic here&quot;<br>    }<br>  ]<br>}</pre><h3>Complete Example: Putting It All Together</h3><p>Here’s a sample workflow that demonstrates the patterns discussed above in a complete workflow.</p><pre>locals {<br>  # Create conditional notification steps<br>  notification_steps = var.enable_notifications ? [<br>    {<br>      &quot;name&quot; : &quot;SendSlackNotification&quot;,<br>      &quot;actionId&quot; : &quot;com.datadoghq.slack.send_simple_message&quot;,<br>      &quot;parameters&quot; : [<br>        {<br>          &quot;name&quot; : &quot;teamId&quot;,<br>          &quot;value&quot; : &quot;MYSAMPLETEAM&quot;<br>        },<br>        {<br>          &quot;name&quot; : &quot;channel&quot;,<br>          &quot;value&quot; : &quot;#alerts&quot;<br>        },<br>        {<br>          &quot;name&quot; : &quot;text&quot;,<br>          &quot;value&quot; : &quot;Alert triggered: {{ Source.monitor.name }}&quot;<br>        }<br>      ],<br>      &quot;display&quot; : {<br>        &quot;bounds&quot; : {<br>          &quot;x&quot; : 0,<br>          &quot;y&quot; : 432<br>        }<br>      }<br>    }<br>  ] : []<br><br>  # Create conditional outbound edges <br>  # If notifications are enabled, route to notification step; otherwise, workflow ends<br>  check_monitor_outbound_edges = var.enable_notifications ? [<br>    {<br>      &quot;nextStepName&quot; : &quot;SendSlackNotification&quot;,<br>      &quot;branchName&quot; : &quot;true&quot;<br>    }<br>  ] : []<br><br>  sample_workflow_name = &quot;Sample-workflow-${var.team_name}&quot;<br>  aws_connection_map = {<br>    # AWS connection IDs for different environments. Already created on datadog console<br>    &quot;sandbox&quot; = { connection_id : &quot;abcd&quot;, label : &quot;MY_SAMPLE_AWS_CONNECTION_1&quot; }<br>    &quot;dev&quot;    = { connection_id : &quot;abcdefg&quot;, label : &quot;MY_SAMPLE_AWS_CONNECTION_2&quot; }<br>  }<br>  aws_connection_id    =  local.aws_connection_map[&quot;sandbox&quot;].connection_id<br>  aws_connection_label = local.aws_connection_map[&quot;sandbox&quot;].label<br>}<br><br>variable &quot;enable_notifications&quot; {<br>  type        = bool<br>  description = &quot;Enable Slack notifications in the workflow&quot;<br>  default     = false<br>}<br><br>resource &quot;datadog_workflow_automation&quot; &quot;sample_workflow&quot; {<br>  name        = local.sample_workflow_name<br>  description = &quot;Sample workflow demonstrating Terraform patterns for Datadog workflow automation&quot;<br>  tags        = var.workflow_tags<br>  published   = true<br><br>  spec_json = jsonencode(<br>    {<br>      &quot;triggers&quot; : [<br>        {<br>          &quot;startStepNames&quot; : [<br>            &quot;CheckMonitorStatus&quot;<br>          ],<br>          &quot;monitorTrigger&quot; : {}<br>        }<br>      ],<br>      &quot;steps&quot; : concat([<br>        {<br>          &quot;name&quot; : &quot;CheckMonitorStatus&quot;,<br>          &quot;actionId&quot; : &quot;com.datadoghq.core.if&quot;,<br>          &quot;parameters&quot; : [<br>            {<br>              &quot;name&quot; : &quot;joinOperator&quot;,<br>              &quot;value&quot; : &quot;or&quot;<br>            },<br>            {<br>              &quot;name&quot; : &quot;conditions&quot;,<br>              &quot;value&quot; : [<br>                {<br>                  &quot;comparisonOperator&quot; : &quot;eq&quot;,<br>                  &quot;leftValue&quot; : &quot;{{ Source.monitor.overall_state }}&quot;,<br>                  &quot;rightValue&quot; : &quot;Alert&quot;<br>                },<br>                {<br>                  &quot;comparisonOperator&quot; : &quot;eq&quot;,<br>                  &quot;leftValue&quot; : &quot;{{ Source.monitor.overall_state }}&quot;,<br>                  &quot;rightValue&quot; : &quot;Warn&quot;<br>                }<br>              ]<br>            }<br>          ],<br>          &quot;outboundEdges&quot; : local.check_monitor_outbound_edges,<br>          &quot;display&quot; : {<br>            &quot;bounds&quot; : {<br>              &quot;x&quot; : 0,<br>              &quot;y&quot; : 0<br>            }<br>          }<br>        }<br>      ], local.notification_steps),<br>      &quot;handle&quot; : local.sample_workflow_name,<br>      &quot;connectionEnvs&quot; : [<br>        {<br>          &quot;env&quot; : &quot;default&quot;,<br>          &quot;connections&quot; : [<br>            {<br>              &quot;connectionId&quot; : local.aws_connection_id,<br>              &quot;label&quot; : local.aws_connection_label<br>            }<br>          ]<br>        }<br>      ],<br>      &quot;inputSchema&quot; : {<br>        &quot;parameters&quot; : [<br>          {<br>            &quot;name&quot; : &quot;MonitorURL&quot;,<br>            &quot;type&quot; : &quot;STRING&quot;,<br>            &quot;defaultValue&quot; : &quot;sample_workflow&quot;<br>          }<br>        ]<br>      }<br>    }<br>  )<br>}</pre><h3>Critical Pitfalls to Avoid</h3><h4>1. Permissions &amp; Ownership</h4><p><strong>Pitfall:</strong> Workflows created via Terraform are owned by Terraform, not by individual users. Standard-role users cannot publish or edit them directly. While this is not necessarily a problem, it could become a blocker in some situations.</p><p><strong>Impact:</strong> Teams may be unable to manage workflows after they’re deployed.</p><p><strong>Solution:</strong> Have an admin grant users permission to publish/unpublish workflows.</p><h4>2. Step Function Limitations</h4><p><strong>Pitfall:</strong> Datadog workflows currently have limited capabilities with Express Step Functions.</p><p><strong>Impact:</strong> Workflow may fail on step functions-related steps.</p><p><strong>Solution:</strong> Prefer Standard Step Functions and document this limitation for your team.</p><h4>3. Datastore Management</h4><p><strong>Pitfall:</strong> Datastores cannot be provisioned via Terraform, and workflows need explicit permissions to access them.</p><p><strong>Impact:</strong> Workflow may fail due to missing datastore access.</p><p><strong>Solution:</strong></p><ul><li>Create your datastore in the Datadog console and reference it by ID in Terraform.</li><li>Grant Terraform a <strong>Manager</strong> role in the datastore:</li></ul><ol><li>Go to the Datastore page in Datadog.</li><li>Click the Settings icon.</li><li>Select Edit Permissions</li><li>Add Terraform with Manager role</li></ol><h4>4. AWS Connection Management</h4><p><strong>Pitfall:</strong> AWS connections are environment-specific and must be pre-created in Datadog.</p><p><strong>Impact:</strong> Workflow will fail if connections are missing or incorrect.</p><p><strong>Solution:</strong></p><ul><li>Create AWS connections for each environment in the Datadog console and reference them by ID in Terraform.</li></ul><ol><li>Click Actions in the Datadog sidebar.</li><li>Select Connections</li><li>Click on the + New Connection button at the top of the connections page.</li><li>Select AWS from the list of possible integrations.</li><li>Complete the form, then copy the generated IAM policy statement (the statement only shows at creation of the connection).</li><li>In the AWS console, create a new role and attach the generated policy statement to it.</li></ol><h3>Benefits Achieved</h3><ol><li><strong>Team Autonomy</strong>: Each team can own and modify their workflow variations independently.</li><li><strong>Environment Consistency</strong>: Identical workflows can be deployed across environments with environment-specific configurations.</li><li><strong>Infrastructure as Code</strong>: Workflows are now part of the infrastructure, enabling automated deployments and rollbacks.</li><li><strong>Documentation</strong>: The Terraform code serves as living documentation of the workflow logic.</li></ol><p>Terraform support for Datadog workflows is a game-changer. It brings version control, consistency, and automation to what used to be a manual, UI-driven process. While the JSON structures can be complicated to set up, the long-term benefits outweigh the initial setup cost.</p><h3>Further Reading</h3><p>For more details on Datadog workflows, here are some resources:</p><ul><li><a href="https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/workflow_automation">Datadog Workflow Automation Terraform Resource</a></li><li><a href="https://registry.terraform.io/providers/DataDog/datadog/latest/docs">Terraform Datadog Provider Documentation</a></li><li><a href="https://docs.datadoghq.com/workflows/">Datadog Workflow Automation Guide</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0673abefe87b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/terraforming-datadog-workflows-0673abefe87b">Terraforming Datadog Workflows</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beyond Deployments: Creative Uses for GitLab Scheduled Pipelines]]></title>
            <link>https://medium.com/runa-engineering/beyond-deployments-creative-uses-for-gitlab-scheduled-pipelines-c3e5ecbd2816?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/c3e5ecbd2816</guid>
            <category><![CDATA[review]]></category>
            <category><![CDATA[scheduled-pipelines]]></category>
            <category><![CDATA[slackbot]]></category>
            <category><![CDATA[gitlab]]></category>
            <category><![CDATA[pull-request]]></category>
            <dc:creator><![CDATA[DIVYA ROSE RAJU]]></dc:creator>
            <pubDate>Mon, 01 Dec 2025 16:32:36 GMT</pubDate>
            <atom:updated>2025-12-01T17:43:50.916Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9OGyTZytKz_HYcCgC5yFIQ.png" /></figure><p>When we talk about GitLab pipelines, we usually think of continuous integrations and deployments — jobs that runs tests, builds artifacts, deploy services. Pipelines are essentially automated tasks that run in response to an event or condition, whether that’s a merge request, a push to a branch, or even a defined schedule.</p><p>Once we look beyond continuous integrations and deployments, pipelines open up an interesting world of possibilities. With a little creativity, they can become your productivity buddy and can keep team workflows in top shape.</p><p>At Runa, we gave a creative sparkle to scheduled pipelines and came up with the Runa Review Bot; a friendly reminder that sends periodic summaries of all open merge requests from our team to our Slack channel.</p><h3>The problem we wanted to solve</h3><ul><li>The Merge Requests(MR) sits idle until the next stand-up, or when the author finally drops a “Hey, can someone review my MR?” reminder.</li><li>Reviewers open a large MR late in the day (say, at 6 PM) and instantly regret it. I have had a lot of such experiences, and I am sure you do too.</li><li>Someone’s waiting on a code review but does not want to nag people on Slack.</li></ul><p>These overall result in delays, context-switching, and often means reviews happen later than they should on something that could have been merged sooner.</p><h3>Why MR Size Matters?</h3><p>Not all MRs are created equal. By showing additions and deletions counts in the Slack summary, we give reviewers the choice.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Vf-bVhuD0Bka4UB1O3V3lA.png" /><figcaption><em>Is this a quick “coffee-break” review? Or is this going to be a “block-off-an-hour” deep dive?</em></figcaption></figure><p>Developers can get a sense of what’s coming and whether they have the bandwidth to review it, without even opening the MR. The size of an MR is not necessarily linked to the time taken to review it. It is more about helping each other plan better and making sure no one gets caught off guard.</p><p>At Runa, we generally prefer small MR’s. We believe it reduce the cognitive load for reviewers, making it easier to spot issues early and provide meaningful feedback. For authors, smaller MRs are easier to test, revert and/or merge, resulting in better review cycles and quality.</p><h3>The Solution we built — The Runa Review Bot</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YjZR-CdrjlqjtZUHlA98Gg.png" /></figure><p>Instead of manually pinging for review on Slack, we built a Gitlab pipeline schedule that runs twice a day — once in the morning and once in the late afternoon.</p><p><strong>Here’s What it Does:</strong></p><ol><li>Scans for open MRs in our repositories.</li><li>Compiles a friendly Slack message with:</li></ol><ul><li>MR title, link and author.</li><li>Short description of what the MR is about.</li><li>MR size (additions + deletions + no. of files changed), so reviewers can decide if they have time to tackle it now or later.</li></ul><p>3. Finally, it sends the summary to the team’s Slack channel, or DM reviewers directly using Slack webhooks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nie8eTiMp_RiY34Z9-Dm4A.png" /><figcaption>A scheduled message from the Runa Review Bot, posted to our the slack channel.</figcaption></figure><p>The scheduled pipeline triggers a Python script twice a day. Under the hood, the script fetches open merge requests, compiles details and posts a message to Slack.</p><pre>workflow:<br>  rules:<br>    - if: &#39;$CI_PIPELINE_SOURCE == &quot;schedule&quot;&#39;<br>      when: always<br>stages:<br>  - review-reminder<br>send_open_merge_requests_to_slack:<br>  stage: review-reminder<br>  variables:<br>    TEAM_NAME: $TEAM_NAME<br>    GITLAB_TOKEN: $GITLAB_TOKEN<br>    SLACK_CHANNEL_WEBHOOK_URL: $SLACK_CHANNEL_WEBHOOK_URL<br>    TEAM_MEMBER_IDS: $TEAM_MEMBER_IDS<br>  script:<br>    - cd runa_review_bot<br>    - poetry install<br>    - poetry run python main.py</pre><p><em>Pseudocode:</em></p><pre>BEGIN<br>  Load environment variables: TEAM_NAME, GITLAB_TOKEN, SLACK_WEBHOOK, TEAM_MEMBER_IDS<br>  Set BASE_URL ← &quot;&lt;https://gitlab.com/api/v4&gt;&quot;<br>  FUNCTION get_diff_stat(project_id, mr_id):<br>    Fetch diffs from GitLab<br>    RETURN additions, deletions, files_changed<br>  FUNCTION get_open_mrs(user_ids):<br>    all_mrs ← []<br>    FOR each user_id IN user_ids:<br>      Fetch open, non-draft MRs for user_id<br>      FOR each MR:<br>        diff_stat ← get_diff_stat(project_id, mr_id)<br>        IF MR can be merged AND no conflicts:<br>          Add MR with diff_stat to all_mrs<br>    RETURN all_mrs<br>  FUNCTION format_for_slack(mrs):<br>    RETURN formatted message with MR title, author, link, and size<br>  FUNCTION send_to_slack(message):<br>    POST message to SLACK_WEBHOOK<br>  FUNCTION main():<br>    mrs ← get_open_mrs(TEAM_MEMBER_IDS)<br>    IF mrs not empty:<br>      send_to_slack(format_for_slack(mrs))<br>    ELSE:<br>      PRINT &quot;No open merge requests.&quot;<br>  main()<br>END</pre><p><strong>Workaround — for when you do not want your MR to be reviewed yet!</strong></p><p>Well, mark it as a draft. Our review bot filters out draft MRs-we know sometimes you’re not ready for feedback, and you might want to hide your “not-so-proud-yet” commits.</p><h3><strong>What’s Improved?</strong></h3><ul><li>Better productivity — Reviewers can plan their day around MR sizes.</li><li>Less Slack noise — No need for repeated manual pings.</li><li>Lower friction — Hiding unfinished MRs as drafts keeps the bot from spamming half-baked work.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rCw3fYzVM6PwYbCmcqP84A.png" /><figcaption>Table shows the a summary of MR review time before and after the Runa Review Bot. <em>This impact summary is based on a random sample.</em></figcaption></figure><p><strong>Behavioral Observations</strong></p><ul><li>Spikes at 9:30 AM and 4:30 PM — correspond exactly to the bot’s scheduled Slack posts.</li><li>Immediate approvals after notifications show the bot boosted MR visibility.</li></ul><h3><strong>Final Thoughts</strong></h3><p>Scheduled pipelines aren’t just for shipping code — they can be communication tools too. By automating MR reminders, we’ve freed up mental bandwidth and reduced the “Did anyone see my MR?” chatter.</p><p>And yes, if you’re feeling cheeky, you can still hide your giant “work in progress” refactors by marking them as drafts. The bot won’t know. 😉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3e5ecbd2816" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/beyond-deployments-creative-uses-for-gitlab-scheduled-pipelines-c3e5ecbd2816">Beyond Deployments: Creative Uses for GitLab Scheduled Pipelines</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Here’s a reliable method to choose the right design pattern]]></title>
            <link>https://medium.com/runa-engineering/heres-a-reliable-method-to-choose-the-right-design-pattern-809960c28ac2?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/809960c28ac2</guid>
            <category><![CDATA[software-design-patterns]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Kitan]]></dc:creator>
            <pubDate>Mon, 27 Oct 2025 08:47:10 GMT</pubDate>
            <atom:updated>2025-10-27T08:47:08.907Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Areliable method to choose the right design pattern" src="https://cdn-images-1.medium.com/max/1024/1*-JKVuduTp3sZPrxPjdbKug.png" /></figure><p>It was two years into my engineering career and I had just started a new job when I first heard the term design patterns. My brain immediately pictured fabric swatches and tailor’s chalk — patterns that help cut clothes into shape. Blame it on my aversion to certain writing styles or my less-than-stellar attention span in CSC 205 lectures back in university. Whatever the reason, I was painfully unaware at the time and had to familiarize myself with the topic fast.</p><p>A quick online search yielded some interesting articles which helped a little, but the real progress happened on the job. I had to not only learn about design patterns but also develop the skill of deciding which pattern worked best for which problem.</p><p>This article breaks down what design patterns are, the categories, and how I choose the right one to solve real problems. My goal? To give you a practical lens for your next coding challenge — so when it’s time to decide, you won’t just guess. You’ll know.</p><h3>Let’s dive in! What is a design pattern?</h3><blockquote><em>A design pattern is a typical solution to a commonly recurring problem.</em></blockquote><p>In software design, we tend to get the same problems in slightly different forms. A design pattern provides us with a blueprint that can be used to solve a class of problems in a maintainable and consistent manner.</p><p>The benefit of design patterns is that they have been tried, tested and documented by other engineers. Which means that you can focus your energy on other aspects of your system without having to figure out from scratch a problem that has been solved before.</p><p>Design patterns generally fall into three categories: Creational, Structural and Behavioural.</p><ul><li>Creational design patterns help to solve problems that involve creating objects (classes, methods, etc) in a flexible manner i.e at runtime</li></ul><p><strong>Creational pattern example — Factory method</strong></p><pre># Selecting a payment processor <br>class PaymentProcessor:<br>    def pay(self, amount):<br>        raise NotImplementedError()<br><br>class PayPalProcessor(PaymentProcessor):<br>    def pay(self, amount):<br>        print(f&quot;Paid {amount} with PayPal&quot;)<br><br>class StripeProcessor(PaymentProcessor):<br>    def pay(self, amount):<br>        print(f&quot;Paid {amount} with Stripe&quot;)<br><br>class PaymentFactory:<br>    @staticmethod<br>    def get_processor(method):<br>        if method == &quot;paypal&quot;:<br>            return PayPalProcessor()<br>        elif method == &quot;stripe&quot;:<br>            return StripeProcessor()<br>        else:<br>            raise ValueError(&quot;Unknown payment method&quot;)<br><br><br>processor = PaymentFactory.get_processor(&quot;paypal&quot;)<br>processor.pay(100)</pre><ul><li>Structural design patterns help to solve problems that involve composing multiple objects into larger, more complex structures.</li></ul><p><strong>Structural Pattern Example — Adapter method</strong></p><pre># Adapting an old logging library to a new interface<br>class OldLogger:<br>    def log_message(self, msg):<br>        print(f&quot;[OLD LOG] {msg}&quot;)<br><br>class LoggerAdapter:<br>    def __init__(self, old_logger):<br>        self.old_logger = old_logger<br><br>    def log(self, message):  # new interface<br>        self.old_logger.log_message(message)<br><br># Client code<br>old_logger = OldLogger()<br>logger = LoggerAdapter(old_logger)<br>logger.log(&quot;Adapter makes old code usable!&quot;)</pre><ul><li>Behavioural design patterns help to solve problems of communication and responsibility sharing between objects.</li></ul><p><strong>Behavioral Pattern Example</strong> — Command pattern</p><pre>class Command:<br>    def execute(self):<br>        pass<br><br>class SayHello(Command):<br>    def execute(self):<br>        print(&quot;Hello, World!&quot;)<br><br>class SayGoodbye(Command):<br>    def execute(self):<br>        print(&quot;Goodbye, World!&quot;)<br><br>class Greeting:<br>    def __init__(self):<br>        self.commands = {}<br>    def register_command(self, name, command):<br>        self.commands[name] = command<br>    def greet(self, name):<br>        if name in self.commands:<br>            self.commands[name].execute()<br>        else:<br>            print(&quot;No such command registered.&quot;)<br><br>greeting = Greeting()<br>greeting.register_command(&quot;hello&quot;, SayHello())<br>greeting.register_command(&quot;goodbye&quot;, SayGoodbye())<br>greeting.greet(&quot;hello&quot;)</pre><h3>The first step in choosing a design pattern is to understand the problem at hand.</h3><blockquote><em>You have to think, what exactly is the problem I am trying to solve here, in the coding sense of it?</em></blockquote><p>Imagine, for instance, that you are building an app to remotely control your home automation system. The remote control has several buttons that each perform different tasks such as: turning lights on, opening the garage door, playing music, etc. What kind of problem is that? Is it about deciding what object to use to perform a task? Is it about creating one giant object made up of many smaller objects? Or is it about how different parts of the system communicate with each other?</p><p>Let’s dig deeper. What kind of design problem is this? Let’s test it against the three categories of patterns.</p><p>First, is this a creational problem? Creational design patterns solve the problem of flexible creation of objects. The problem in this case is not how to create the devices, we are struggling with triggering actions on them. So, this is not a creational problem.</p><p>Next, we need to ask if this problem can be solved by having one giant complex object that has different smaller objects. If our remote control needs to assemble several devices into one object or if the different buttons need to share internal connections, this would be a good fit but each button is independent in this case, there is no composition of objects hence, this is not a problem for the structural design pattern.</p><p>Finally, could this be a problem of communication between objects and assigning responsibilities? The remote buttons need a way to request that a certain home device perform an action, without knowing the details of how that action is performed. We are looking for a pattern that separates the action of the button “invoking a request” from the action of “executing the request” so that they can be generally independent. This seems to be a problem of communication between objects which is definitely a behavioural pattern problem.</p><h3>Now, let us look for a specific behavioural pattern that best solves this problem.</h3><p>There are lots of behavioural patterns which lead us to the next challenge. How to know which behavioural design pattern is best suited for this problem?</p><p>Let’s restate our remote control problem. We want our remote control button to send a request to a device which would execute the request. For the device to successfully execute the request, the request has to contain all necessary information.</p><p>Of all the different behavioural patterns, the command pattern stands out for being the closest in definition to this problem.</p><blockquote><em>The command pattern allows us to turn a request into a stand-alone object that contains all the information needed to perform an action. This object can be passed along between two different objects or queued for later execution, or used to trigger an event. This standalone object is called a </em><strong><em>command.</em></strong></blockquote><p>And there we have it, the right pattern for solving the problem! Of course, not every design problem is this easy to classify, but thinking through the categories and matching the problem to each pattern’s definition is a reliable way to make the right choice.</p><p>Now that we’ve found the right pattern, let’s have a look at a simple implementation of the command pattern for our home remote control app.</p><pre>from abc import ABC, abstractmethod<br><br># Command Interface – Defines the execute() method that all commands must implement.<br>class Command(ABC):<br>    @abstractmethod<br>    def execute(self):<br>        pass<br><br><br># Receiver – The actual device that performs the work (Light, Blinds, etc.).<br>class Light:<br>    def on(self):<br>        print(&quot;Light is ON&quot;)<br><br>    def off(self):<br>        print(&quot;Light is OFF&quot;)<br><br><br>class Blinds:<br>    def close(self):<br>        print(&quot;Blinds are CLOSED&quot;)<br><br>    def open(self):<br>        print(&quot;Blinds are OPEN&quot;)<br><br><br># Concrete Commands – Implement specific actions (turn lights on, close blinds).<br>class LightOnCommand(Command):<br>    def __init__(self, light):<br>        self.light = light<br><br>    def execute(self):<br>        self.light.on()<br><br><br>class BlindsCloseCommand(Command):<br>    def __init__(self, blinds):<br>        self.blinds = blinds<br><br>    def execute(self):<br>        self.blinds.close()<br><br><br># Invoker – The remote control that triggers the commands.<br>class RemoteControl:<br>    def __init__(self):<br>        self.buttons = {}<br><br>    def set_command(self, button_name, command):<br>        self.buttons[button_name] = command<br><br>    def press(self, button_name):<br>        if button_name in self.buttons:<br>            self.buttons[button_name].execute()<br>        else:<br>            print(&quot;No command assigned to this button.&quot;)<br><br><br># Client – The setup code that binds buttons to commands.<br>if __name__ == &quot;__main__&quot;:<br>    # Receivers<br>    living_room_light = Light()<br>    blinds = Blinds()<br><br>    # Commands<br>    light_on = LightOnCommand(living_room_light)<br>    blinds_close = BlindsCloseCommand(blinds)<br><br>    # Remote setup<br>    remote = RemoteControl()<br>    remote.set_command(&quot;A&quot;, light_on)<br>    remote.set_command(&quot;B&quot;, blinds_close)<br><br>    # Press buttons<br>    remote.press(&quot;A&quot;)  # Output: Light is ON<br>    remote.press(&quot;B&quot;)  # Output: Blinds are CLOSED</pre><p>Choosing the right behavioral pattern isn’t about memorizing definitions — it’s about understanding what each one unlocks. Once you recognize the shape of the problem, the right solution starts to reveal itself. The more you lean into that mindset, the more intuitive these decisions become.</p><h3>Further Reading</h3><p>If you’d like to explore design patterns in more detail, here are some excellent resources:</p><ul><li><a href="https://refactoring.guru/design-patterns/what-is-pattern">What’s a design pattern?</a></li><li><a href="https://www.geeksforgeeks.org/system-design/software-design-patterns/">Design Patterns Tutorial</a>.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=809960c28ac2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/heres-a-reliable-method-to-choose-the-right-design-pattern-809960c28ac2">Here’s a reliable method to choose the right design pattern</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Discomfort Zone: Onboarding Into an Unfamiliar Tech Stack]]></title>
            <link>https://medium.com/runa-engineering/the-discomfort-zone-onboarding-into-an-unfamiliar-tech-stack-d64351d41c1c?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/d64351d41c1c</guid>
            <dc:creator><![CDATA[Nikolay Stoyanov]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 09:53:43 GMT</pubDate>
            <atom:updated>2025-10-13T09:53:37.556Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gWBFSuhbu6IamCNo4rm_dw.png" /></figure><p>Before joining Runa, my development world revolved around .NET. Every company I’d worked for used a similar architecture, tools, and “ways of working”.</p><p>Over the years, I became very comfortable in that environment — I knew the frameworks, the patterns, and where the tricky bugs usually hid. But that familiarity also meant fewer surprises, and I was ready for something that would push me to learn again.</p><p>Then I joined Runa — a team made up of people from all kinds of technology backgrounds, bringing different perspectives and ways of thinking. In practice, our architecture is firmly rooted in AWS and serverless, but the diversity of experience on the team meant I’d be learning a lot of new approaches.</p><p>On my very first day, everything was new. And I decided to lean into that.</p><p>I was offered a choice between a Windows or MacOS machine. I could have stayed in my comfort zone since I had only used Windows professionally… but instead I went with MacOS. If I was going to shake things up, I might as well go all in.</p><p>That meant learning hotkeys, window management, and a whole new interface from scratch. Luckily, my colleagues jumped in with tips, shortcuts, and “must-know” tricks that made the transition a little less bumpy.</p><p>Little did I know, that same “lean into the new” mindset would define my first three months.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*Da3hW59n7Mz9bqWM.png" /></figure><p><strong>First impressions and feelings</strong></p><p>In the first few days, I was hit with terms like <em>serverless</em>, <em>lambdas</em>, <em>step functions</em>, <em>API Gateway</em> 🤯 — all completely new to me.</p><p>It was exciting, but also a little overwhelming — a mix of new tools, rapid learning, and the occasional <em>“Is this job for me? Am I a good developer?”</em> moment.</p><p>What kept me moving forward was Runa’s well-structured onboarding plan. It gave me milestones to hit, resources to explore, and documentation that made the learning curve feel less like a cliff.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/0*efl56GMlFegc_IiD.png" /></figure><p>I still remember closing my first ticket after just one week.</p><p>It was a low-priority task — just a couple of lines of simple code for a bug fix — but it felt incredibly satisfying to contribute something tangible to the team so early on ✅.</p><p>After that first win, I knew I could keep making progress — but I also knew I’d get there much faster by learning directly from my teammates. That’s where pair programming came in.</p><p><strong>Pair Programming</strong></p><p>With many new domain specific acronyms, tools, and even a brand-new IDE to get used to, I quickly realised I could spend days just trying to figure things out on my own.</p><p>Instead of reinventing the wheel, I started pairing with different teammates.</p><p>Each session was like a guided tour of the codebase — seeing how others approached problems, exploring different areas of the project and architecture, and walking through diagrams of the system.</p><p>This wasn’t just about learning shortcuts; it gave me a deeper understanding of how things fit together and how I could start making meaningful contributions.</p><p>One pairing session in particular stands out. We have two repositories with almost identical names 🙈. I had been quietly wondering for some time which was used for what. My teammate walked me through the difference, what they are used for and when we should make changes to them. It was such a simple explanation, but it instantly cleared up a big source of confusion and made the codebase feel a lot less intimidating.</p><p>That kind of teamwork not only saved me time, but I could now see more clearly how everything fit together and how I could start contributing effectively.</p><p>As I got more familiar with the codebase, I also started noticing how we made technical decisions — and that’s when I was introduced to something completely new for me: technical decision documents.</p><p><strong>Technical Decision Documents</strong></p><p>In previous companies, technical decisions often happened in quick calls or came as top-down instructions from someone in a more senior position. We’d agree on a solution, implement it, and move on — but rarely did we document the why behind those choices.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/659/0*oBq5_GnTWh5gVLjh.png" /></figure><p>At Runa, it works differently. Every significant technical decision is captured in a <em>decision document</em>. It’s a structured write-up where we outline the problem, propose different solutions, list the pros and cons of each, and record the reasoning behind the final choice.</p><p>At first, it felt like extra work — until I wrote a few of them.</p><p>I realised how valuable they are especially when you’re new and trying to understand why something was built a certain way. You can trace the thinking that led to a particular solution without having to dig through endless chats or schedule extra meetings.</p><p>My first decision doc was… rough 😅. The structure was sloppy, I missed some important details, and it took several rounds of feedback before it was finally accepted. But that back-and-forth forced me to fully understand the problem I was solving. By the time it was accepted, I knew exactly what to build — and I could jump straight into implementing my tickets without hesitation.</p><p>But even with all that documentation, there were moments when I needed answers right away — and that’s when AI became one of my onboarding accelerators.</p><p><strong>And, of course, AI</strong></p><p>Everyone’s talking about AI these days — how powerful it is, how risky it might be, whether it will replace developers.</p><p>For me, its biggest impact during onboarding was much simpler: it helped me understand the codebase faster.</p><p>When I was stuck trying to figure out how a feature worked, I could ask AI to walk me through it — from where it was built, to where it was used, to how it was tested.</p><p>Having that map in my head made it much easier to extend features or fix issues without feeling like I was poking around in the dark.</p><p>I also found AI surprisingly useful when working on automated tests 🤖✨. It could suggest a rough starting point, which I could then refine and adapt to fit the project.</p><p>All these tools and habits I’d picked up in my first weeks — from decision docs to pairing and even AI — were about to be tested when I joined my first large-scale initiative: a design sprint.</p><p><strong>Design sprints and POCs</strong></p><p>At Runa, when we face bigger, more open-ended challenges, we tackle them with design sprints. We split into smaller groups, spend a few days exploring possible solutions, and build quick POCs (proofs of concept) to see how those ideas would work in practice.</p><p>These sprints aren’t just about coding fast — they’re about experimenting, validating assumptions, and making sure we choose the right direction before committing to a full build. Decision documents play a big role here too, capturing our thinking so it’s clear why we moved forward with one idea over another.</p><p>Recently, we kicked off an initiative to solve a specific problem, and we gave ourselves the time and space to experiment. We came up with three very different solutions — not just in code, but also in the infrastructure they required. While building the POCs, we changed our minds multiple times, each time spotting where that solution might fail, become overly complex for our use case, or simply not deliver the value we expected.</p><p>It was a little exhausting to switch directions so often, but every change taught us something new. Instead of clinging to ideas, we treated each discarded POC as a step toward finding the right approach.</p><p>Working on these POCs turned out to be one of the most valuable learning experiences of my first three months. I could try out ideas in a low-risk environment, get quick feedback, and see firsthand how we make decisions that shape the future of the product.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/651/0*c1sWm-dvOM1Lsnni.png" /></figure><p><strong>Key Takeaways</strong></p><p>Looking back, my first three months at Runa were a mix of steep learning curves, small wins, and a lot of “aha” moments.</p><p>I joined from a completely different tech background, learned an entirely new stack, and went from being overwhelmed by acronyms to shipping meaningful work.</p><p>Pair programming sessions, technical decision documents, AI assistance, and design sprints all played their part in helping me get there.</p><p>If I had to sum up my onboarding into a few lessons, they’d be:</p><ul><li><strong>Lean into the unknown.</strong> From a completely new tech stack to tools I’d never touched, every unfamiliar step helped me grow.</li><li><strong>Ask, pair, and learn from others.</strong> Every teammate sees the system from a different angle, and that perspective is gold when you’re new.</li><li><strong>Don’t get attached to your first idea.</strong> The best solution often comes after you’ve explored (and thrown away) a few others.</li><li><strong>Write things down.</strong> Decision docs aren’t just for the team — they’re a gift to your future self.</li><li><strong>Use the tools that help you learn faster.</strong> Whether it’s AI or just good documentation, find your accelerators.</li></ul><p>Three months ago, I was wondering if I could keep up — now I’m contributing, learning, and excited for what’s next.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d64351d41c1c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/the-discomfort-zone-onboarding-into-an-unfamiliar-tech-stack-d64351d41c1c">The Discomfort Zone: Onboarding Into an Unfamiliar Tech Stack</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fast, Reliable Serverless: From 50 minutes to 50 seconds a year of downtime on AWS]]></title>
            <link>https://medium.com/runa-engineering/fast-reliable-serverless-from-50-minutes-to-50-seconds-a-year-of-downtime-on-aws-88bd5b1b330b?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/88bd5b1b330b</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[aws-step-functions]]></category>
            <category><![CDATA[response-time]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[serverless-architecture]]></category>
            <dc:creator><![CDATA[danny]]></dc:creator>
            <pubDate>Thu, 10 Oct 2024 15:26:11 GMT</pubDate>
            <atom:updated>2024-10-10T21:11:30.841Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>At Runa, we’re always striving to build the most performant products possible. I work on the Ledger team, a sub-team of engineering. Our focus is to enable each engineering team to transfer money with speed, accuracy, and ease.</p><p>Our approach to solving this problem is to create an interface over our core ledger. This prevents our other teams from getting bogged down with the complexities of this source of truth. This has allowed them to move with speed and efficiency while keeping the core ledger accurate and intact.</p><p>We achieve this through a set of APIs. They make up the Payment Intents service. In simple terms, they are like your bank’s concept of authorizing and settling a transaction.</p><p>This service is integral to our platform’s functionality, as fundamentally, the purpose of Runa is to move value with ease. Two key requirements, besides correctness, were high reliability (&gt; 99.999%) and a fast response time.</p><p>We are plagued by what we call “phantom” requests. These are requests that AWS API Gateway receives, but never reach the resource, or return. AWS API Gateway has an SLA of just 99.95%, meaning that for a large volume endpoint, you are almost guaranteed to have requests getting lost in the network.</p><p>Below is a high level diagram of our architectural components. We often see the request from the API Gateway never reaching the Step Function, resulting in a timed-out request.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zd-WyvmezAubdY0cSgZbHQ.png" /><figcaption>High Level Architectural Diagram</figcaption></figure><p>For an example of how the various SLA’s of AWS components can be compounded, see this post: <a href="https://www.linkedin.com/posts/theburningmonk_heres-a-reminder-that-the-benchmark-for-activity-7117198103497056256-s01D/">Yan Cui — Compounding SLA’s</a></p><p>We used the below methodologies to take our API’s from 99.99% to 99.997% (99.999% after discounting the phantom errors out of our control) across 4 million requests.</p><h3>Observability</h3><p>Everything here starts with observability. Observability encompasses all of our performance metrics, logging and incident management.</p><p>We must use a data-driven, scientific approach to improve performance. Often, things thought to improve performance do not, and vice versa. Without these starting benchmarks you have no indication of whether your change has had a positive or negative effect.</p><p>At a high level, the two metrics we care about for these benchmarks are <a href="https://www.notion.so/5ff32ba1ae9a4e41b8e4aa1605ae5f11?pvs=21">p95 response time</a>, and your 4xx/5xx error code rate. The server error/timeout reasoning is obvious. However, 4xx errors have nuances. Is your authoriser failing? Is a resource that should exist missing? Are you hitting rate limits? These should be tackled on a case-by-case basis.</p><p>Now, what tools do we use to get our benchmarks?</p><h4>Performance Testing</h4><p>To get where you need to be, you need to know what your starting point is. To do this, you’ll need to implement a performance test; it will give you a baseline for the responsiveness and stability under certain workloads. It is the basis for evaluating any changes we have made, to see if it has actually improved things! Without this you could worsen response time, or increased errors, which only occur under certain workloads.</p><p>At Runa, we have used <a href="https://www.artillery.io/">Artillery</a> extensively to perform our load testing. It’s a very simple tool to set up and use. It enables you to achieve distributed load testing and provides you with a breakdown of the stats: response time, error codes etc.</p><p>It provides you with excellent visualisations of these metrics, and allows you to compare previous runs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rjzEPp7rvC8fl0_JANzkyg.png" /><figcaption>Artillery Report</figcaption></figure><p>We used Artillery to measure all of the incremental changes to improve response time and reliability of our Payment Intents system. It highlighted multiple race condition issues and bottlenecks that we were not aware of until extensively testing.</p><h4>Visualisation</h4><p>Artillery gives us multiple snapshots of performance, but it does not show us a long term experience of our API users.</p><p>For this, at Runa, we have 2 options, Datadog, or AWS CloudWatch. We use Datadog for our longer term dashboards, but whilst troubleshooting ad-hoc issues or incidents, we lean on CloudWatch.</p><h4>Summary</h4><p>The two tools above should give you an idea of your system’s performance. Every system has its failure and slow points, and diagnosing them accurately can be a challenge. We’ll address this challenge in the next section.</p><h3>Diagnosis Tooling</h3><h4>Tracing</h4><p>Tracing gives us detailed execution diagnostics. It shows each component of an operation, from the gateway to the database. It can help you identify points of failure, slow components, and give you a better idea about your components’ topology.</p><p>Simply enable AWS X-Ray tracing on all your components; e.g Api Gateway resource, step function, lambda, etc.</p><p>With a few lines of configuration you can get a detailed map of the service, like below, with detailed execution durations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gq7cpRaATf_cESm_WlB7TA.png" /><figcaption>AWS X-Ray Trace Map</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zcNjE48E6W-6ukas_dS5Rg.png" /><figcaption>AWS X-Ray Trace Waterfall</figcaption></figure><p>You can then use trace map analytics to aggregate and analyze these durations, giving you a detailed view of each component’s performance. This is essential to ensure you get the full picture of the response time of the component, and not just one offs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cUrvm0WhTZoiVGf9lk-wcQ.png" /><figcaption>AWS X-Ray aggregated statistics</figcaption></figure><h4>Logs Insights</h4><p>If you use Lambda’s in your service, Logs Insights can help extract metrics that effect performance:</p><pre>filter @type = “REPORT” <br>| stats <br> count(@type) as countInvocations , <br> count(@initDuration) as countColdStarts ,     (count(@initDuration)/count(@type))*100 as percentageColdStarts,<br> max(@initDuration) as maxColdStartTime,<br> avg(@duration) as averageDuration,<br> max(@duration) as maxDuration,<br> min(@duration) as minDuration,<br> avg(@maxMemoryUsed) as averageMemoryUsed,<br> max(@memorySize) as memoryAllocated,  (avg(@maxMemoryUsed)/max(@memorySize))*100 as percentageMemoryUsed <br>by bin(1h) as timeFrame</pre><p>You can generate insights like the maximum cold boot duration for your lambda over time, shown in the graph below. Combining this with the number of cold boots during that period provides a solid basis for understanding what affects your response time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4yOW0P2qcfWqXcou0oFaZA.png" /><figcaption>AWS Logs Insights graph for lambda statistics</figcaption></figure><h4>Summary</h4><p>These tools help us figure out what’s slow or unreliable. With these baselines, we can take a scientific approach to tweak, test, and iterate on improvements.</p><h3>Lessons Learned</h3><h4>Avoid lambda layers where possible</h4><p>Sharing code with Lambda Layers has been advocated as a simple way to reduce the amount of code you’re deploying, and improve cold boot duration. This is a common fallacy that is explained in far more detail in this <a href="https://aaronstuyvenberg.com/posts/why-you-should-not-use-lambda-layers">article</a>. You should read this separately, but the headlines are as follows:</p><ul><li>Lambda Layers do not improve or reduce cold boot duration</li><li>The size of the code you load is the sole biggest factor in cold start time. It doesn’t matter if it’s in your lambda or a layer.</li><li>Lambda layers can be a significant pain in the development process, as it means you will have “automagically” appearing dependencies in your lambda deployment. Debugging these is a PITA.</li><li>Lambda layers do not support semantic versioning, just a LATEST and incremental versions. This means that if you make a major version change that affects multiple lambdas, all of them must be ready to handle this change at the same time.</li><li>Dependency collisions are more likely using Lambda Layers.</li></ul><p>It’s also worth noting that the Datadog extension, which enables APM traces, can increase cold boot duration, as discussed in this article: <a href="https://medium.com/@adtanasa/the-latency-overhead-of-application-performance-monitoring-with-aws-lambda-extensions-896582da9b51">https://medium.com/@adtanasa/the-latency-overhead-of-application-performance-monitoring-with-aws-lambda-extensions-896582da9b51</a></p><p>Datadog has recently released an updated version of this layer, reducing the cold start impact of APM from 800ms to 400ms. However, this is still a significant delay, which is why the ledger team has chosen to use AWS Xray tracing for our critical low-latency lambdas where necessary.</p><h4>Improve your cold start duration</h4><p><strong>Package size</strong></p><p>The sole largest contributor to cold start duration is the size of your zip file. There is little way round decreasing your cold start duration without addressing this. The graph below shows the effect of package size on cold start durations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*T1_d_3htuyISsqZ5c_cWZQ.png" /><figcaption>AWS Lambda cold start duration by deployment size: <a href="https://mikhail.io/serverless/coldstarts/aws/">https://mikhail.io/serverless/coldstarts/aws/</a></figcaption></figure><p>Steps you can take to address this:</p><ul><li>Check for all non-essential dependencies and remove where possible.</li><li>Split up your internal dependencies into smaller modules that can be selectively bundled.</li><li>Use only the sub-parts of dependencies that you need, e.g @aws-sdk → @aws-sdk/dynamodb/client.</li><li>Move any dependencies required for development/testing to dev-dependencies.</li><li>Major improvements can be seen by using an adapted script that trims all non-essential files from node_modules. You can find a version of this script here: <a href="https://github.com/tuananh/node-prune/blob/develop/prune.sh">https://github.com/tuananh/node-prune/blob/develop/prune.sh</a>. We used this script to reduce our node modules size from ~14mb to ~400kb.</li></ul><p>We recommend using <a href="https://pnpm.io/">https://pnpm.io/</a> which ensures your lambdas are packaged only with what they need, reducing your bundle size dramatically.</p><p>These steps cut our P95 time by around 60ms for our critical endpoints — nearly a 20% reduction. This improvement came entirely from faster cold boot durations, which means bursty workloads are much less affected by cold starts. You can test these bursts of request volume in artillery by configuring different phases with quick ramp-up times.</p><p><strong>Keep your lambda outside of VPCs where possible</strong></p><p>VPCs are necessary for accessing certain resources like Redis and Postgres clusters. In your service, it might be tempting to put all your lambdas inside the VPC for easier development, but this approach increases your cold start duration.</p><p>Of course this is not always achievable, but be conscious of this!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/996/1*FMHkUADIIj8D-O2XnS5jZg.png" /><figcaption>Cold start durations inside &amp; outside of a VPC: <a href="https://mikhail.io/serverless/coldstarts/aws/">https://mikhail.io/serverless/coldstarts/aws/</a></figcaption></figure><p><strong>Use AWS Lambda Power Tuning</strong></p><p><a href="https://github.com/alexcasalboni/aws-lambda-power-tuning">https://github.com/alexcasalboni/aws-lambda-power-tuning</a></p><p>AWS Power Tuning is a tool that you can use to optimise the amount of memory that your Lambda should be configured to use.</p><p>Memory in AWS Lambda’s is your 1 stop shop lever for increasing CPU, RAM, and network priority. Up to a point, it can yield big performance gains. See the graph below for the results of one of our Lambdas. We saved 50ms (33%!) off of our initialisation duration for a single config variable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o0uLXcshK4bsBvVq5a-Vlw.png" /><figcaption>AWS Lambda Power Tuning results</figcaption></figure><p>The graph above shows a sweet spot at 4096 (circled). There, invocation time and cost converge.</p><p>In our case, it’s not worth going much higher. The effect on performance improvements is minimal — it’s suspected that higher memory takes longer for AWS to allocate to a point.</p><p>Also worth knowing, is that the cost graph isn’t strictly true. It does not account for the decreased execution duration, below is an example from AWS over 1000 executions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pmmGJrTNdo03aPwYpqLcWg.png" /><figcaption>Table of cost by execution duration and memory: <a href="https://repost.aws/knowledge-center/lambda-memory-compute-cost">https://repost.aws/knowledge-center/lambda-memory-compute-cost</a></figcaption></figure><p><strong>Initialise clients outside of the Lambda handler</strong></p><p>A crucial step in improving your Lambda’s performance is to reuse clients like Redis or DynamoDB between requests. To do this, make sure to initialize them outside of the Lambda handler.</p><p>This is because AWS Lambda uses a concept called “execution context” to improve performance. When a Lambda function is invoked, AWS Lambda creates an execution context to run the function. This context is maintained for some time in anticipation of another function invocation.</p><p>For example, the simple change below saved us 80ms off our p95 response time for this endpoint.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hdJhItebxgmATKSICvlcng.png" /><figcaption>Initialising the Redis client outside of the Lambda handler</figcaption></figure><p>This change will yield a small increase in your initialisation duration the first time a Lambda is booted, but the subsequent requests will benefit greatly.</p><h4><strong>Favour direct integrations over generic calls from code</strong></h4><p>Where possible, using a step function direct integration is preferred, rather than integrating via Lambda code &amp; boto/aws-sdk. This approach reduces latency and the duration of Lambda invocations, ultimately decreasing overall cost.</p><p>For simple endpoints, using a Lambda function is often sufficient. However, when you need to orchestrate multiple steps, components, and service calls, it’s highly recommended to wrap your operation in a Step Function. AWS Step Functions give you out of the box observability, flexibility in extending your service, and, most importantly, leading on to the next point: native error handling and <strong>retries.</strong></p><h4><strong>Retry, retry, retry</strong></h4><p>This was learnt the hard way by the Ledger team, when we first released an early version of our Payment Intents service, we were being paged more than 20 times per week. All points of failure that could fail, did. Even retrieving secrets from SecretsManager!</p><p>To fix this, the team had to put in a lot of effort, adding retries to everything and making all calls that create a resource idempotent. As mentioned earlier, for instance, API Gateway’s SLA is only 99.95%, but since our client teams need to meet their 4 9’s SLOs, we had to achieve at least 5 9&#39;s.</p><p>This means that any operation involving a network call — whether it’s an API Gateway invocation, retrieving secrets, or publishing events — must be assumed to potentially fail, so you need to catch and retry it. Additionally, your services should be designed to be idempotent, allowing reliant API consumers to safely retry as well.</p><p>Using this methodology, we went from more than 20 errors a week, down to 0 errors over a 10 day period.</p><p>Step Function retries are much more robust and easier to implement than building these features into custom Lambda functions. We highly recommend this approach for a more reliable and observable service.</p><p>Our biggest takeaway from earlier versions was the importance of this approach, and we’ve made heavy use of it in our latest implementation to handle flaky network requests. Anywhere there’s a network call, it can break, and it will.</p><p>When setting up retries, be thoughtful about your configuration. Consider what you’re retrying and what you’re hitting. For example, we retry Redis operations five times in five milliseconds, but when dealing with legacy systems, we use a more gradual approach with exponential backoff. Each case is unique.</p><h4><strong>Can you do it asynchronously?</strong></h4><p>We can’t avoid the fact that our operations are slow and take a long time due to various reasons, such as relying on legacy services, third-party processors, or other slow dependencies.</p><p>Since these operations are critical but not time-sensitive and are required to generate a response to the client, they should be completed asynchronously. This can be done by invoking a step function asynchronously or using one of the many message brokers offered by AWS. You can find a comparison of their latencies for put and delivery here: <a href="https://lucvandonkersgoed.com/2022/09/06/serverless-messaging-latency-compared/">https://lucvandonkersgoed.com/2022/09/06/serverless-messaging-latency-compared/</a></p><p>The key is to keep critical operations simple and do as little work as possible before sending a response to the client.</p><p>By taking slow operations out of the critical path, we’ve significantly reduced our P95 latencies.</p><h4><strong>Use the correct component to store your configuration</strong></h4><p>This article provides more in-depth information than I will cover here: <a href="https://aaronstuyvenberg.com/posts/ultimate-lambda-secrets-guide">https://aaronstuyvenberg.com/posts/ultimate-lambda-secrets-guide</a></p><p>In summary, for configurations that don’t require frequent updates and aren’t sensitive, store them in environment variables instead of Parameter Store — it’s much faster and encrypted at rest.</p><p>For critical secrets that need quick retrieval, use Secrets Manager instead of Parameter Store secure strings.</p><p>Also, remember to use the retry policy mentioned earlier when fetching secrets in a Step Function. If you’re using secrets in a Lambda, consider retrieving them during initialization, outside of the handler, like we did with clients.</p><h4><strong>A note on provisioned concurrency</strong></h4><p>Provisioned concurrency, in simple terms, means having a set number of Lambdas always running, which avoids the cost of cold starts.</p><p>When configuring your Lambda, it can be tempting to use provisioned concurrency as the default option. However, this can be very expensive. For instance, a 4096mb Lambda with a provisioned concurrency of 10 would cost over £300 a month. If you have multiple Lambdas, the costs add up quickly.</p><p>If your Lambdas are serving a high volume of requests, you don’t really need provisioned concurrency — Lambdas stay active after the current execution for an <em>unspecified</em> amount of time, however with a high throughput you will almost definitely end up re-using already warm instances.</p><p>The CloudWatch metric ProvisionedConcurrencySpilloverInvocations can be a good indicator of whether your provisioned concurrency is not being used and you could afford to scale it back.</p><h4><strong>Where possible use resource based IAM policies</strong></h4><p>Using resource-based IAM policies instead of identity/role-based ones can significantly reduce the response time between your API gateway and compute resource, such as Step Functions or Lambdas. This simple change can produce dramatic results. For more details, see this excellent article: <a href="https://medium.com/@lancers/low-hanging-fruit-to-reduce-api-gateway-to-lambda-latency-8109451e44d6">https://medium.com/@lancers/low-hanging-fruit-to-reduce-api-gateway-to-lambda-latency-8109451e44d6</a></p><h3>Summary</h3><ul><li>Start with your benchmarks, you don’t know if you’ve improved things if you don’t know where you started.</li><li>Tracing gives you highly detailed diagnostic capabilities.</li><li>Logs insights can give you deeper insight to your Lambda init, cold start durations, and memory usage.</li><li>Think about your overall Lambda size &amp; whether it’s in a due to the impact on response time.</li><li>Initialise clients so that they can be reused across requests.</li><li>Step functions orchestrate even small operations, thanks to their retry and direct integration capabilities.</li><li>If it can be async, it should be async.</li><li>Use resource based IAM policies.</li></ul><h3>Recommended resources</h3><ul><li>AWS Well Architected — build secure, high-performing, resilient, and efficient infrastructure: <a href="https://aws.amazon.com/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-lens-whitepapers.sort-order=desc&amp;wa-guidance-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-guidance-whitepapers.sort-order=desc">https://aws.amazon.com/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-lens-whitepapers.sort-order=desc&amp;wa-guidance-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-guidance-whitepapers.sort-order=desc</a></li><li>Use direct service integrations to optimize your architecture: <a href="https://aws.amazon.com/blogs/architecture/use-direct-service-integrations-to-optimize-your-architecture/">https://aws.amazon.com/blogs/architecture/use-direct-service-integrations-to-optimize-your-architecture/</a></li><li>What are lambda extensions and why do they matter? <a href="https://lumigo.io/blog/aws-lambda-extensions-what-are-they-and-why-do-they-matter/">https://lumigo.io/blog/aws-lambda-extensions-what-are-they-and-why-do-they-matter/</a></li><li>Cold start time analysis: <a href="https://medium.com/@r_smith/aws-lambda-memory-duration-and-cold-start-time-analysis-bfcd78d2e58a">https://medium.com/@r_smith/aws-lambda-memory-duration-and-cold-start-time-analysis-bfcd78d2e58a</a></li><li>Improving Lambda cold starts: <a href="https://www.youtube.com/watch?v=IHFJ-0_3cXY">https://www.youtube.com/watch?v=IHFJ-0_3cXY</a></li><li>Improve performance of services by avoiding unnecessary parameter retrieval: <a href="https://aaronstuyvenberg.com/posts/ultimate-lambda-secrets-guide">https://aaronstuyvenberg.com/posts/ultimate-lambda-secrets-guide</a></li><li>Low hanging fruit to reduce AWS API Gateway Latency: <a href="https://medium.com/@lancers/low-hanging-fruit-to-reduce-api-gateway-to-lambda-latency-8109451e44d6">https://medium.com/@lancers/low-hanging-fruit-to-reduce-api-gateway-to-lambda-latency-8109451e44d6</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=88bd5b1b330b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/fast-reliable-serverless-from-50-minutes-to-50-seconds-a-year-of-downtime-on-aws-88bd5b1b330b">Fast, Reliable Serverless: From 50 minutes to 50 seconds a year of downtime on AWS</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from npm to pnpm: A Journey of Decisions and Experiences]]></title>
            <link>https://medium.com/runa-engineering/migrating-from-npm-to-pnpm-a-journey-of-decisions-and-experiences-c907480c912e?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/c907480c912e</guid>
            <category><![CDATA[pnpm]]></category>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Phuc Le]]></dc:creator>
            <pubDate>Wed, 20 Sep 2023 12:59:04 GMT</pubDate>
            <atom:updated>2023-09-27T20:27:50.740Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*a_AOf97yuxL4TKjwNOoAxQ.png" /></figure><p>Are you ready to embark on a journey from npm to pnpm? If you’ve been contemplating this migration, it’s essential to understand that the process might vary depending on the complexity of your project. I have read many articles on migration from npm to pnpm but it is mostly surface-level guidance. Let’s dive into some important decisions and share experiences that can make this transition smoother.</p><h4><strong>1. Choosing the Right Time for Migration ⏰</strong></h4><p>The first and most crucial decision you need to make is to select the perfect time for the migration. If your project is large and involves multiple developers, it’s best to plan the migration during a time of reduced development activity or during a dedicated sprint. Why?</p><ul><li>Folder structure changes during migration can lead to conflicts if parallel development is ongoing.</li><li>Even if you use `pnpm import` to generate a `pnpm-lock.yaml` from another package manager’s lockfile, issues may still arise. Ensuring all dependencies are the same can be tricky.</li><li>You may need time to handle regressions and have a backup plan if things go wrong.</li></ul><p>Choosing the right time for migration will make your life easier, reduce pressure, and minimise potential bugs.</p><h4><strong>2. Understanding the Root Cause and Deep Diving into Issues 🌱</strong></h4><p>During migration, you might encounter unexpected problems, such as unit tests failing even when all dependencies and libraries seem fine. In such situations, it’s essential to resist the temptation to simply fix the failing tests. Instead, take the time to investigate the root cause.</p><p>For example, I faced unit test failures after migrating. After some research, I discovered that an incompatible node version in my updated `pnpm` version was affecting the reactivity of Vue.js. Fixing the tests alone wouldn’t have addressed the real problem, and the actual features of the application wouldn’t have worked correctly either. Upgrading the Vue.js library along with `pnpm` resolved the issue and improved overall performance.</p><h4><strong>3. Proper Regression Testing with Backup Plans </strong>🔄</h4><p>Once you are confident with the migration through your own testing, it’s time for comprehensive regression testing with a solid backup plan.</p><p>Changing the package manager can introduce issues here and there, due to slight differences in the way each package manager operates. Instead of merging your migration branch into the main branch immediately, set up a sandbox environment and point it to the migration branch. Observe and test it over a specific timeframe, similar to your production setup. This way, you can easily revert to the current main branch if any unexpected problems arise.</p><p>Although maintaining two branches might require extra effort, it will be worth it in the long run if you’ve chosen the right time for migration as advised earlier.</p><h4><strong>4. And more…</strong></h4><p>In real-world scenarios, you may also need to address related components such as Storybook, TypeScript, and CI/CD configurations. However, the three decisions above are the most important to think about before starting your journey from npm to pnpm.</p><h4><strong>Conclusion </strong>🚀</h4><p>Migrating from npm to pnpm is indeed an adventure, and you’re right to expect more than just surface-level guidance. Understanding the complexity of your project, choosing the right time for migration, investigating compatibility issues, conducting comprehensive testing, and considering the full scope are all critical steps that contribute to a successful migration.</p><p>Don’t be discouraged by potential challenges; instead, embrace the journey with a proactive and thorough approach. By doing so, you’ll gain valuable experience and insights that will help you navigate future migrations with confidence. Happy migrating!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c907480c912e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/migrating-from-npm-to-pnpm-a-journey-of-decisions-and-experiences-c907480c912e">Migrating from npm to pnpm: A Journey of Decisions and Experiences</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why You Should Consider a Low-Code Approach]]></title>
            <link>https://medium.com/runa-engineering/how-to-design-a-forex-service-on-aws-8802dd50cf8a?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/8802dd50cf8a</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[forex]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[fintech]]></category>
            <dc:creator><![CDATA[Dinmohamed Danabek]]></dc:creator>
            <pubDate>Wed, 05 Apr 2023 13:22:49 GMT</pubDate>
            <atom:updated>2023-08-29T13:26:33.513Z</atom:updated>
            <content:encoded><![CDATA[<p><em>How to leverage AWS to create a distributed Foreign Exchange (FX) service that exchanges currencies in real-time.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jMgBhmuB9YxCsMHfkgD26g.png" /></figure><p>At Runa’s Treasury team, our mission is to make the flow, accounting, and reporting of financial value seamless and efficient for our customers with 100% confidence at scale. We are excited to announce a new FX service to provide an effortless and instant currency exchange experience on the platform.</p><p>Runa FX delivers real-time currency exchange using AWS technologies. The long-term vision of this service is to provide customers with the ability to place orders in their base currency to buy assets in different currencies while conversions are done on the fly behind the scenes. Therefore, our service needs to be scalable and highly available to handle thousands of transactions per second.</p><p>Let’s dive into some of the big-bang decisions we have made to design this service with a low-code approach.</p><h4>Database</h4><p>Choosing the correct database was critical in our design to ensure low latency and high throughput. The main database operation was to retrieve quotes by different parameters. We initially considered DynamoDB and AuroraDB because they align with Runa’s serverless architecture vision. However, after weighing the options, we opted for DynamoDB, as it met our requirements for low latency, high availability, and scalability and did not require ACID transactions.</p><p>Another consideration was DocumentDB, which offered flexibility with schema and queries. It is a fully managed database with minimal configuration and is highly available. Choosing this approach would encourage us to fail fast as we can always redesign the schemas per new requirements whereas in DynamoDB it is critical to get the design right from the start. However, a deeper analysis revealed that DocumentDB’s scalability was limited compared to DynamoDB for our use case. In DocumentDB, the primary database instance handles both read and write operations, which could become a bottleneck for our write-heavy service.</p><p>Ultimately, DynamoDB proved to be the best fit for our FX service, offering low latency and high scalability, while ensuring the data was highly available.</p><h4>Serverless orchestration</h4><p>Our team’s goal from the start was to create a new and independent FX service from scratch. We considered using Lambdas in conjunction with Simple Notification Service (SNS), Simple Queue Service (SQS), or AWS Step Functions to manage workflows between components. AWS Step Functions emerged as the clear choice for our solution because our use case did not require a complex event-driven architecture. Instead, our priority was to have a simple and sequential design that was easy to debug. Our previous experience showed us that managing a serverless system with too many Lambdas can be challenging and difficult to scale.</p><p>The power of Step Functions lies in its ability to act as a ”function orchestrator” that connects the different steps of our application. The steps can handle tasks such as decision-making, retries, parallel processes, error handling, and timeouts, which results in less code to write and maintain. Step Functions also offer direct integrations with other AWS services such as DynamoDB, S3, EC2, and Batch, allowing us to reduce the number of Lambdas we need and improving the overall latency of the FX service.</p><p>In the diagram below, we can see an example of our step function setup for exchanging currencies. It is responsible for getting a quote from a provider, performing calculations, and storing the result in our database. The ability to visualise the steps allows our team to effectively debug, monitor, collaborate, and optimise because each transition can be reviewed in more detail without diving into any code. At each step, we directly integrate with another AWS service, perform data transformations, handle errors, and set up retry logic that can be easily configured.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/908/1*dox-WpbigctR4UZQXJWhSQ.png" /><figcaption>Step function design to exchange currencies</figcaption></figure><h4>Deployment</h4><p>The primary cloud service provider for Runa is AWS, and different teams and repositories use different Infrastructure as Code (IaC) tools for development and deployment. The four tools in active use are Terraform, SAM, Cloudformation, and the AWS CDK. We used this opportunity to review our existing deployment tools and make a change if needed. After, careful consideration, we went with Terraform for various reasons.</p><p>One of the most appealing factors was its declarative language (Hashicorp Configuration Language), which makes development easier, and its ability to use modules, making it easier to reuse code, read, and maintain. Terraform also has a wider range of providers available, which was crucial in choosing it over the other IaC tools.</p><p>Additionally, our team considered the rollback mechanism, drift detection, and deployment security when making this decision. While the AWS CDK provides a rollback mechanism, Terraform’s advantages in these other areas made it the preferred choice for this project.</p><h4>Summary</h4><p>In the end, our team has achieved a low-code solution with only 300 lines of Python code. We utilised the AWS serverless stack to do most of the work for us, eliminating the maintenance and operation hassle that comes with writing code. To tie everything together we used Terraform to create, manage and deploy all of our resources. This low-code solution has several advantages, including reducing the time and resources required for development and maintenance, improving scalability and reliability, and enabling faster iteration and innovation. You can find the final FX architecture below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TvmrDI1XKjqfMJUxS4Qhow.png" /><figcaption>High-level Forex architecture design</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8802dd50cf8a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/how-to-design-a-forex-service-on-aws-8802dd50cf8a">Why You Should Consider a Low-Code Approach</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Talent Sourcer to Talent Partner: 5 things I’ve learnt along the way]]></title>
            <link>https://medium.com/runa-engineering/from-talent-sourcer-to-talent-partner-5-things-ive-learnt-along-the-way-e8f98f81a403?source=rss----acc23e73e72f---4</link>
            <guid isPermaLink="false">https://medium.com/p/e8f98f81a403</guid>
            <category><![CDATA[talent-acquisition]]></category>
            <category><![CDATA[hiring-tips]]></category>
            <category><![CDATA[talent]]></category>
            <category><![CDATA[startup-life]]></category>
            <category><![CDATA[hiring-for-startup]]></category>
            <dc:creator><![CDATA[Sydney Borrow]]></dc:creator>
            <pubDate>Tue, 25 Oct 2022 11:34:54 GMT</pubDate>
            <atom:updated>2022-10-25T11:34:54.833Z</atom:updated>
            <content:encoded><![CDATA[<p><em>There’s more to it than filling roles</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_DpsnHpuV1JN-RVsiIfmhw.jpeg" /></figure><p>I’m Sydney, a Talent Partner here at WeGift. I joined 6 months ago as hire number 2 in the Talent Team. If I’m being honest, I was nervous to get started. Fintech and payments felt slightly out of my comfort zone and something I could barely begin to explain. But I was excited. I could see that the product being built was innovative, it was smart and it was solving a problem; a problem I understood.</p><p>And I was right to be excited. In just 6 months, I’ve been promoted from Talent Sourcer to Talent Partner, and experienced huge professional growth. WeGift was only my second role in the Talent space and I originally joined as a Talent Sourcer, a similar role to my previous experience. As a Talent Sourcer, I was primarily responsible for pooling candidates, outreaching and first screening calls, before passing them over to a Talent Partner to be progressed through the process. At WeGift, however, I was encouraged to be involved in all stages of the recruitment process, starting by shadowing everything from technical screens through to presenting insights in reporting syncs. I was supported in decision making and actively given opportunities to receive and challenge feedback. Learning through osmosis was valuable for seeing the autonomy the teams have in their roles, with the freedom to be creative in their approach to solving problems and, although not always right, seeing how they owned, digested, reflected on mistakes created a comfortable environment for me to do the same. This enabled me to upskill quickly, and the transition to Talent Partner was a natural step forward.</p><p>As I look back on my journey so far, I’ve reflected on the skills that I’ve learnt which have contributed to me becoming an effective Talent Partner.</p><p><strong>Candidate Experience is Key</strong></p><p>We’re in an incredibly competitive market, even with what appears to be a difficult economic time on the horizon, and we find ourselves competing with equally exciting businesses who are looking to secure the same top talent. By providing a candidate experience that’s both seamless and supportive, without compromising on time, we are able to attract and retain high quality candidates, while successfully competing with these other high growth businesses. Being committed to building high performing teams by hiring great people contributes to the success of candidate referrals. Great people know great people, and a positive candidate experience gives the best possible chance of being an employer of choice for world class talent. Being a Talent Partner gives the opportunity to consistently improve candidate experience, a metric which should be closely monitored. For example, initial calls should be a two way discovery. Of course, we want to find out as much as possible about a candidate’s background, experience and motivations, but equally there must be adequate time for the candidate to ask questions and for us to share the mission and exciting future plans of the business. If it’s looking to be a good fit, a full overview of the process and each stage is given, including who they’ll be meeting and what to expect.</p><p><strong>Build Trust with Hiring Managers</strong></p><p>Hiring Managers are one of the most important stakeholders of a Talent Partner and the relationship that is built with them will contribute to the success of hiring. Keep them updated, organise a recurring sync meeting every other day to run through pipeline health, candidates in process, market insights and reporting metrics. Be honest with them when facing blockers with a role, present the opportunity for their support in order to work on ways to overcome them collaboratively. It’s building this trust that enables you to adapt to changing circumstances, set and commit to expectations, be honest about challenges and feel empowered to make decisions.</p><p><strong>Actively seek learning opportunities</strong></p><p>We are all always learning, evolving in our careers and developing skills. Coaching from a Manager, prepared resources and learning sessions are great ways to gain skills, but taking accountability for personal learning can accelerate your development. Committing to new things can be a great way to learn. The thought of doing something for the first time can be nerve wracking, but with support from peers, when given the opportunity, go for it. Take a risk, but evaluate the learnings, whether successful or not. Ask lots of questions, proactively search for different resources and expect to make mistakes — just learn from them quickly.</p><p><strong>Embrace challenges, they promote creativity</strong></p><p>Challenges happen on a daily basis, they keep our roles exciting and ensure we continue to learn. For example, most of us in Talent will have managed a tough role. This could be anything from niche domain expertise to specific industry experience, and after running multiple extended ‘boolean’ searches with little rewards, it can become frustrating. However, this creates an opportunity for us to think more creatively about our sourcing methods. Market mapping is a great way to extend the candidate pool, identify and engage with top talent who may not otherwise surface in searches. Attending events and listening to different seminars helps to expand networks, while also providing alternative ways to uncover and attract talent. There are a number of different groups and communities full of top professionals, so joining these and immersing into the content and conversations they provide can yield great results, both from a hiring and learning perspective.</p><p><strong>Consistently look for improvements in the hiring process</strong></p><p>Often recruitment processes are predefined, but that does not mean that they cannot be improved. Aim to build a process that enables you to move fast and reduce time to hire, while also allowing candidate capabilities to be effectively measured. And continually iterate this. Data provides valuable insights into the success and potential blockers of the hiring process. Utilise passthrough data to evaluate which stages are bottlenecks, seek a greater insight into the reasoning and consider altering the process to resolve this. As well as internally assessing the hiring processes, external feedback is hugely valuable too. Building a strong working relationship with your candidates enables you to gain true and honest feedback about the hiring process, which can drive meaningful change.</p><p>In summary, my transition from Talent Sourcer to Talent Partner is something I’m incredibly proud of, but I recognise that I am still very early into my talent career. I’m excited for the future, looking forward to what I can learn and the impact I can make. I hope to reflect in the upcoming years and add the above learnings, striving to be the most effective Talent Partner I can be. I’ve highlighted what I believe makes a great Talent Partner and I’d love to hear your thoughts or ideas in the comments!</p><blockquote>If you’re looking to work alongside a team of super talented individuals and join us on our mission to build the future of digital payouts, check out our <a href="https://wegift.io/careers/">careers page!</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e8f98f81a403" width="1" height="1" alt=""><hr><p><a href="https://medium.com/runa-engineering/from-talent-sourcer-to-talent-partner-5-things-ive-learnt-along-the-way-e8f98f81a403">From Talent Sourcer to Talent Partner: 5 things I’ve learnt along the way</a> was originally published in <a href="https://medium.com/runa-engineering">Engineering at Runa</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>