<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>architecture on Daniel Adams Tech</title>
    <link>https://blog.danieladamstech.com/tags/architecture/</link>
    <description>Recent content in architecture on Daniel Adams Tech</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 31 Aug 2023 20:00:00 -0400</lastBuildDate>
    <atom:link href="https://blog.danieladamstech.com/tags/architecture/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>IAM Assert Role</title>
      <link>https://blog.danieladamstech.com/2023/iam-assert-role/</link>
      <pubDate>Thu, 31 Aug 2023 20:00:00 -0400</pubDate>
      <guid>https://blog.danieladamstech.com/2023/iam-assert-role/</guid>
      <description>Back at the start of 2021, I delved into a small curiosity project around how to assert ownership of an AWS role to a non-AWS entity. I implemented an API Gateway Sigv4 signer in a Spring RestTemplate Interceptor . Later we integrated that design in a production app. That security integration has had zero issues since. As a thought experiment, I wanted to see if it was possible to use an IAM root of trust when calling other endpoints besides API Gateway.</description>
      <content:encoded><![CDATA[<p>Back at the start of 2021, I delved into a small curiosity project around how to assert ownership of an AWS role to a non-AWS entity. I implemented an API Gateway Sigv4 signer in a 
<a href="https://www.baeldung.com/spring-rest-template-interceptor" target="_blank" rel="noopener">Spring RestTemplate Interceptor</a>
. Later we integrated that design in a production app. That security integration has had zero issues since. As a thought experiment, I wanted to see if it was possible to use an IAM root of trust when calling other endpoints besides API Gateway. AWS Sigv4 authentication is a symmetric scheme, but to keep things simple with fewer security resources on the verification side, I knew I needed to use asymmetric verification.</p>
<p>This idea was inspired by CyberArk DAP 
<a href="https://docs.cyberark.com/Product-Doc/OnlineHelp/AAM-DAP/Latest/en/Content/Operations/Services/AWS_IAM_Authenticator.htm" target="_blank" rel="noopener">IAM Authenticator integration</a>
 and 
<a href="https://developer.hashicorp.com/vault/docs/auth/aws#iam-auth-method" target="_blank" rel="noopener">Hashicorp Vault IAM authenticator.</a>
 Vault has a lot of cool technology in it! For another side project, I tried combining the 
<a href="https://en.wikipedia.org/wiki/Shamir%27s_secret_sharing" target="_blank" rel="noopener">Shamir Secret Sharing</a>
 
<a href="https://github.com/hashicorp/vault/blob/main/shamir/shamir.go" target="_blank" rel="noopener">package from Vault</a>
 with the 
<a href="https://en.wikipedia.org/wiki/Format-preserving_encryption" target="_blank" rel="noopener">Format-Preserving Encryption</a>
 
<a href="https://github.com/capitalone/fpe" target="_blank" rel="noopener">package from CapitalOne</a>
 into my project 
<a href="https://github.com/danieladams456/shamirfpe/blob/master/shamirfpe.go" target="_blank" rel="noopener"><code>shamirfpe</code></a>
.</p>
<p>Back to the topic at hand. The client wanting to assert ownership of an IAM role will pre-sign an STS 
<a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html" target="_blank" rel="noopener"><code>GetCallerIdentity</code></a>
 request, which can then be executed by a trusted entity. Handing over this signature will not compromise the security of the IAM role secret keys. The trusted entity will get a response directly from AWS STS, which can be trusted. We then know the caller has that specific role session. Even administrators will not be able to generate application tokens if they are not trusted in the assume role policy. This maintains the same security posture as IAM-based access to AWS resources.</p>
<p>The presign and execute process could be done on every app request, but making a network call every time is slow and susceptible to throttling. We instead sign that assertion with 
<a href="https://aws.amazon.com/blogs/security/digital-signing-asymmetric-keys-aws-kms/" target="_blank" rel="noopener">KMS asymmetric keys</a>
. The <code>RSASSA_PKCS1_V1_5_SHA_256</code> algorithm supported by KMS is the same one used for <code>RS256</code> signed JWTs. Constructing the appropriate JWT header and body allows us to issue industry-standard tokens that can be verified by client libraries across many languages. In the standard JWT verification process, the verifier can have multiple public key IDs to support rotation of a specific key upon compromise. The backup signing key[s] can have deny policies in KMS during normal operation. If a key is compromised, it can be scheduled for deletion thereby disabling the key. The next key can be enabled for usage without applications having to always check the public key endpoint.</p>
<p>Below is a sequence diagram representing the three parties (client, assertion validator, and target resource) and two AWS services (STS and KMS.)</p>
<p><img loading="lazy" src="https://images.danieladamstech.com/2023-aws-assert-role.png" alt="AWS Assert Role Sequence Diagram"  />
</p>
<p>My previous Sigv4 signer integration was in Java using the AWS SDK 
<a href="https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/AWS4Signer.html" target="_blank" rel="noopener">AWS4Signer</a>
, so I wanted to use some other languages for this exercise.</p>
<ul>
<li>Request formulation and presigning
<ul>
<li>
<a href="https://github.com/danieladams456/aws-assert-role/blob/main/presign-get-caller-identity/presign-via-middleware.js" target="_blank" rel="noopener">JavaScript via AWS SDK middleware</a>
. This is a more roundabout hack method, but the SDK manages pulling credentials from the metadata endpoint and maintaining a fresh session token component</li>
<li>
<a href="https://github.com/danieladams456/aws-assert-role/blob/main/presign-get-caller-identity/presign-via-sigv4.js" target="_blank" rel="noopener">JavaScript via AWS SDK SignatureV4</a>
 module. The code is very straightforward, but doesn&rsquo;t pull the credentials for you.</li>
<li>The cleanest way is using the external dependency from Michael Hart 
<a href="https://github.com/mhart/aws4" target="_blank" rel="noopener"><code>aws4</code></a>
</li>
</ul>
</li>
<li>
<a href="https://github.com/danieladams456/aws-assert-role/blob/main/verify-get-caller-identity/verifier.py" target="_blank" rel="noopener">Posting to STS, signing with KMS, and constructing JWT</a>
 - Python</li>
<li>
<a href="https://github.com/danieladams456/aws-assert-role/blob/main/local-kms-verification/verifier/verify.go" target="_blank" rel="noopener">JWT verification</a>
 - Go</li>
</ul>
<p>The main shortcoming of this idea and why it isn&rsquo;t practical for use by itself in a production scenario is it only addresses authentication, not authorization. I believe AWS IAM roles are about the best root of trust available, but a good authorization system is necessary. Hashicorp Vault and CyberArk bring robust implementations on the authorization side, but that is not something I would want to code custom.</p>
<p>This is where AWS VPC Lattice comes in. I was very excited to hear about it when I attended Re:Invent last year. I switched my schedule around to go to a talk by the Lattice team. AWS API Gateway private APIs to backends over PrivateLink accomplishes something similar from a security perspective but has a much more complex set of AWS resources. That is worth it when you are trying to achieve API governance, but sometimes you just want a simple connection with good security. The VPC lattice target group construct achieves this perfectly. VPC lattice uses IAM-format 
<a href="https://docs.aws.amazon.com/vpc-lattice/latest/ug/auth-policies.html" target="_blank" rel="noopener">auth policies</a>
, very similar to API Gateway IAM authentication. I was hoping that the 
<a href="https://docs.aws.amazon.com/vpc-lattice/latest/ug/auth-policies.html#auth-policies-resource-format" target="_blank" rel="noopener">ARN format</a>
 would be a stable identifier based on service name so they would still work if a service was deleted and recreated, but it seems to be a random ID like API Gateway. I looking forward to reading more about and hopefully playing with VPC Lattice in the future.</p>
]]></content:encoded>
    </item>
    <item>
      <title>System Dependencies</title>
      <link>https://blog.danieladamstech.com/2023/system-dependencies/</link>
      <pubDate>Thu, 24 Aug 2023 20:00:00 -0400</pubDate>
      <guid>https://blog.danieladamstech.com/2023/system-dependencies/</guid>
      <description>Cloud Service Dependencies To quote Werner Vogles, &amp;ldquo;Everything fails all the time.&amp;rdquo; When designing an app, we want to carefully evaluate what dependencies it requires. Cloud services are highly available, but the union of many can still lead to a measurable decrease in availability. Dependencies that are not absolutely necessary should fail open to allow the system to continue doing critical work. Below are two examples I have personally run into in the past couple of years.</description>
      <content:encoded><![CDATA[<h3 id="cloud-service-dependencies">Cloud Service Dependencies</h3>
<p>To quote Werner Vogles, 
<a href="https://cacm.acm.org/magazines/2020/2/242334-everything-fails-all-the-time/abstract" target="_blank" rel="noopener">&ldquo;Everything fails all the time.&rdquo;</a>
 When designing an app, we want to carefully evaluate what dependencies it requires. Cloud services are highly available, but the union of many can still lead to a measurable decrease in availability. Dependencies that are not absolutely necessary should fail open to allow the system to continue doing critical work. Below are two examples I have personally run into in the past couple of years.</p>
<h4 id="caching-proxy-dynamodb-write">Caching Proxy DynamoDB Write</h4>
<p>We had a web app with the UI layer running out in AWS that used an authentication system on-prem. Session validation on every page load and Ajax call was causing performance issues, so I wrote a session caching service out in AWS to decrease the latency for validation. It was a huge success for the user experience, saving over 15 hours of waiting time per day. The service used a two-tier cache system: local cache within the service and DynamoDB within the cluster. Cache misses called the on-prem system.</p>
<p>The 
<a href="https://aws.amazon.com/message/12721/" target="_blank" rel="noopener">December 7th, 2021 issue</a>
 caused a DynamoDB outage for us. According to the postmortem, using VPC endpoints to connect to DynamoDB was what caused the impact. The service was correctly set to opportunistically write the response back to DynamoDB. The issue was my lookup only caught my custom <code>TokenNotFoundException.</code> DynamoDB failures threw a different exception which my code was not expecting. I made a quick fix to go to origin on any failure, but we ended up waiting out the outage and not trying to deploy during it. Only a portion of users were affected, and we didn&rsquo;t want to make the situation worse by the ongoing outage causing a deployment issue. After the outage and much testing with simulated conditions like removing IAM access to the table, we deployed the change to production.</p>
<h4 id="docker-log-driver-ring-buffer">Docker Log Driver Ring Buffer</h4>
<p>One issue that hit us out of left field was the 
<a href="https://aws.amazon.com/message/11201/" target="_blank" rel="noopener">Kinesis outage of November 25th, 2020.</a>
 We had on-prem and ECS Docker services that froze up and were unable to deploy new. None of our applications used Kinesis, so what was going on? It turned out that the Docker log drivers can actually block the applications if logs aren&rsquo;t accepted. Cloudwatch logs depended on Kinesis and therefore couldn&rsquo;t ingest logs. I was surprised this was default behavior so 
<a href="https://github.com/moby/moby/issues/41714" target="_blank" rel="noopener">opened a Github issue</a>
 asking if there was any plan to change. We set all our services to non-blocking with a buffer after that event. AWS later released 
<a href="https://aws.amazon.com/blogs/containers/choosing-container-logging-options-to-avoid-backpressure/" target="_blank" rel="noopener">a blog post</a>
 addressing this issue and presenting the options tradeoffs.</p>
<h3 id="application-service-dependencies">Application Service Dependencies</h3>
<p>Which cloud services you use might be a bit more in your purview, but sometimes what other application services you interact with (especially if owned by other teams) can be necessitated by the requirements. We still should take explicit inventory of those dependencies. Having that information will allow us to better share knowledge about the system as well as prepare us for responding to incidents. If a downstream service encounters an outage, we can know what to expect from our system.</p>
<p>Knowing dependency ordering to bring up the whole system from a hard-down outage is important. Figuring that out before an incident will be much easier than a chat storm during. Extra care must be taken when trying to identify circular dependencies that can inhibit the application from coming up successfully. A recent interesting read along those lines is Gergely Orosz&rsquo;s article 
<a href="https://newsletter.pragmaticengineer.com/p/inside-the-datadog-outage" target="_blank" rel="noopener">Inside Datadog’s $5M Outage.</a>
 Many times these circular dependencies are at the control plane level. This is why we try to use services with managed control planes instead of running those ourselves. However, circular dependencies can still exist within applications, so we must be careful with those as well.</p>
<h3 id="health-check-dependencies">Health Check Dependencies</h3>
<p>Last week, I saw a service in Dev that was continually flapping unable to come up healthy. After looking at the ECS statuses and logs, it became apparent that the health check was failing, and the health check was failing due to a synthetic DynamoDB read failing. That health check was looking up a particular test data record that was no longer there. The dev database had probably been cleared at some point to start over fresh which caused the service to start flapping.</p>
<p>Health checks should depend on another service (cloud or app) only if the <strong>only</strong> way to recover from that service being down is to restart your container. This should almost never be the case as a service should be able to reinitialize connections to its dependencies without a full restart.</p>
<p>The AWS Builders Library has a great 
<a href="https://aws.amazon.com/builders-library/implementing-health-checks/" target="_blank" rel="noopener">article about things to watch for when implementing health checks</a>
. Two choice quotes below:</p>
<blockquote>
<p>Dependency health checks are appealing because they act as a thorough test of a server’s health. Unfortunately they can be dangerous because a dependency can cause a cascading failure throughout a system.</p>
</blockquote>
<blockquote>
<p>The difficulty with health checks is this tension between, on the one hand, the benefits of thorough health checks and quickly mitigating single-server failures and, on the other hand, the harm done by a false positive failure across the entire fleet. Thus, one of the challenges of building a good health check is to guard carefully against false positives. In general, this means that the automation surrounding health checks should stop directing traffic to a single bad server but keep allowing traffic if the entire fleet appears to be having trouble.</p>
</blockquote>
<p>Hopefully this post has given some food for thought about being cognizant of system dependencies, criticality of them, and plans for when they fail.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Wasm for Platforms</title>
      <link>https://blog.danieladamstech.com/2023/wasm-for-platforms/</link>
      <pubDate>Mon, 12 Jun 2023 22:00:00 -0400</pubDate>
      <guid>https://blog.danieladamstech.com/2023/wasm-for-platforms/</guid>
      <description>Today we will follow up on my previous post about programmable platforms and delve into WebAssembly as an implementation option. I have read about WebAssembly on the CloudFlare blog since 2019, but my interest piqued the other day when listening to the Software Snack Bites podcast about WebAssembly . The 1.0 spec was published in December 2019 which started a marked uptick in awareness. Notable implementations include Wasmtime by the Bytecode Alliance (writers of the spec) and the CNCF project WasmEdge .</description>
      <content:encoded><![CDATA[<p>Today we will follow up on my 
<a href="/2023/platforms-not-products/">previous post about programmable platforms</a>
 and delve into WebAssembly as an implementation option. I have read about 
<a href="https://blog.cloudflare.com/tag/webassembly/" target="_blank" rel="noopener">WebAssembly on the CloudFlare blog</a>
 since 2019, but my interest piqued the other day when listening to the 
<a href="https://shomik.substack.com/p/5-matt-butcher-ceo-fermyon-and-steve" target="_blank" rel="noopener">Software Snack Bites podcast about WebAssembly</a>
. The 
<a href="https://www.w3.org/TR/wasm-core-1/" target="_blank" rel="noopener">1.0 spec</a>
 was published in December 2019 which started a marked uptick in awareness. Notable implementations include 
<a href="https://wasmtime.dev/" target="_blank" rel="noopener">Wasmtime</a>
 by the 
<a href="https://bytecodealliance.org/" target="_blank" rel="noopener">Bytecode Alliance</a>
 (writers of the spec) and the CNCF project 
<a href="https://wasmedge.org/" target="_blank" rel="noopener">WasmEdge</a>
.</p>
<blockquote>
<p>
<a href="https://www.w3.org/TR/wasm-core-1/#introduction%E2%91%A2" target="_blank" rel="noopener">From the spec:</a>
 WebAssembly (abbreviated Wasm) is a safe, portable, low-level code format designed for efficient execution and compact representation. Its main goal is to enable high-performance applications on the Web, but it does not make any Web-specific assumptions or provide Web-specific features, so it can be employed in other environments as well.</p>
</blockquote>
<p>WebAssembly was originally developed for browser execution of optimized C++ via JavaScript bindings. As the specification solidified, other people saw promise on the server side. WebAssembly has several nice properties for serverless multi-tenancy. It is secure by default and only exposes interfaces through capabilities. On the performance side, it has &ldquo;zero cold start penalty.&rdquo; This is HUGE for serverless platforms. One of the tradeoffs a developer must account for when deciding whether to go serverless or not has traditionally been acceptable p99 latency due to cold starts.</p>
<p>WebAssembly was designed for web levels of forward compatibility. If there were any backwards incompatible changes, a new binary format version would be created, but the expectation of that is 
<a href="https://www.w3.org/TR/wasm-core-1/#modules%E2%91%A0%E2%93%AA" target="_blank" rel="noopener">&ldquo;very infrequently, if ever.&rdquo;</a>
 This is good news for both application and tooling developers, who can expect a stable set of system interface specifications that will not change.</p>
<p>Building platforms is a natural fit for Wasm. SaaS customers need ways of embedding custom logic in workflows within the platform. One way of delivering that capability is through rules engines or custom 
<a href="https://en.wikipedia.org/wiki/Domain-specific_language" target="_blank" rel="noopener">DSLs</a>
, but those are more constrained in the capabilities they offer. Securely and performantly executing customer-provided code provides the open-ended avenue necessary to unleash developer creativity.</p>
<p>Correctness of custom code is always the responsibility of the developer, but platforms hosting user code can take away much of the other complexity. The user does not have to worry about reachability and latency of API calls from the platform out to his endpoint. The &ldquo;glue&rdquo; is all managed by the platform host and generally provides the best end-user experience. A good example of platform-hosted and external extensibility in the same platform is Snowflake&rsquo;s 
<a href="https://docs.snowflake.com/en/developer-guide/snowpark/python/creating-udfs" target="_blank" rel="noopener">Snowpark UDFs</a>
 and 
<a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/" target="_blank" rel="noopener">External Functions</a>
. Sometimes a requirement for exclusive control might keep you from putting a piece of functionality into a platform, but in most other cases, the reliability and performance lean strongly towards submitting user code for the platform to manage and run.</p>
<p>I would like to call out two examples of these in the real world. 
<a href="https://shopify.dev/docs/apps/functions" target="_blank" rel="noopener">Shopify functions</a>
 allow developers to customize the behavior of the platform ranging from 
<a href="https://shopify.dev/docs/apps/selling-strategies/discounts/experience" target="_blank" rel="noopener">discounts</a>
, 
<a href="https://shopify.dev/docs/apps/checkout/payment-customizations" target="_blank" rel="noopener">payment customizations</a>
, 
<a href="https://shopify.dev/docs/apps/checkout/validation/server-side" target="_blank" rel="noopener">custom validations</a>
, and more. 
<a href="https://shopify.dev/docs/apps/functions/language-support" target="_blank" rel="noopener">Targeting WASM</a>
 allows developers to pick the most appropriate language for the task. High level logic can quickly be written in JavaScript or TypeScript, and computationally intensive operations can be optimally implemented in Rust. Picking 10 of the highest customer-requested &ldquo;logic choke points&rdquo; within the app and opening those up for extensibility can remove large burdens from the SaaS development teams. Customers that were previously blocked and have the resources to invest in customization can self-serve.</p>
<p>The other instance of Wasm support is in a &ldquo;platform for platforms.&rdquo; It&rsquo;s like platform-ception! 
<a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/" target="_blank" rel="noopener">Cloudflare Workers for Platforms</a>
 provides a managed execution environment for customer-supplied code with developer-friendly 
<a href="https://developers.cloudflare.com/workers/platform/bindings/about-service-bindings/" target="_blank" rel="noopener">zero-cost abstractions</a>
. They allow Workers for Platforms customers to have an 
<a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/platform/limits/" target="_blank" rel="noopener">unlimited number</a>
 of scripts so all the end users can submit their customizations. If I was building a SaaS application, I would be very interested in trying this out.</p>
<p>As always, there is no completely free lunch. Platform hooks are just like any other API in that they must be kept stable. Increasing API surface before you know you have the correct abstraction can lock you into a design and prevent refactoring down the road. 
<a href="https://changelog.com/podcast/531#transcript-68" target="_blank" rel="noopener">This interview</a>
 with Nathan Sobo about building the 
<a href="https://github.blog/2022-06-08-sunsetting-atom/" target="_blank" rel="noopener">Atom</a>
 editor at GitHub (and his new editor 
<a href="https://zed.dev/" target="_blank" rel="noopener">Zed</a>
) highlights this. Looking back, Nathan said they focused on extensibility a little too much when building Atom. Some of those decisions, like allowing extension code to run on the main thread instead of 
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode&rsquo;s</a>
 more constrained 
<a href="https://microsoft.github.io/language-server-protocol/" target="_blank" rel="noopener">Language Server Protocol</a>
 design, lead to inflexibility down the road.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Platforms, Not Products</title>
      <link>https://blog.danieladamstech.com/2023/platforms-not-products/</link>
      <pubDate>Sun, 04 Jun 2023 15:00:00 -0400</pubDate>
      <guid>https://blog.danieladamstech.com/2023/platforms-not-products/</guid>
      <description>Back in April, I read one of Gergley&amp;rsquo;s newsletters on Steve Yegge and Developer Productivity . It was a very insightful and enjoyable read, but the part that stood out to me was not the main topic of the post. It was a semi-famous (but unknown to me) piece Steve wrote after six years of tenure at both Amazon and Google. Known as Stevey&amp;rsquo;s Google Platforms Rant , it contrasts Amazon&amp;rsquo;s and Google&amp;rsquo;s execution and mindset.</description>
      <content:encoded><![CDATA[<p>Back in April, I read one of Gergley&rsquo;s newsletters on 
<a href="https://newsletter.pragmaticengineer.com/p/steve-yegge" target="_blank" rel="noopener">Steve Yegge and Developer Productivity</a>
. It was a very insightful and enjoyable read, but the part that stood out to me was not the main topic of the post. It was a semi-famous (but unknown to me) piece Steve wrote after six years of tenure at both Amazon and Google. Known as 
<a href="https://gist.github.com/chitchcock/1281611" target="_blank" rel="noopener">Stevey&rsquo;s Google Platforms Rant</a>
, it contrasts Amazon&rsquo;s and Google&rsquo;s execution and mindset. Even though Google did almost everything in a technically superior way, Amazon came away with the winning platform.</p>
<p>Amazon stressed that their internal APIs between their internal product teams must be the same level of quality as external product APIs. Steve&rsquo;s piece lists challenges and learnings along the way, but the end result was a programmable platform. The focus on solid internal APIs mirrors Chick-fil-a&rsquo;s Enterprise Architecture principle 
<a href="https://medium.com/chick-fil-atech/ea-principles-series-design-for-composability-90f5e12ffecf" target="_blank" rel="noopener">Design for Composability</a>
.</p>
<blockquote>
<p>&ldquo;A product is useless without a platform, or more precisely and accurately, a platform-less product will always be replaced by an equivalent platform-ized product.&rdquo; - Steve Yegge</p>
</blockquote>
<p>One of the biggest reasons to build a platform is you cannot please everybody. Users&rsquo; ability to extend and tweak your software could be the difference between it fitting their use case or not. Two types of platforms exist. Some are services other developers will build apps on top of, like S3. These were never meant to be used in isolation. Another variety is an application designed to be extensible by the end user, like Shopify. The user can begin with the out-of-the-box experience and later customize it with hooks.</p>
<p>I read Steve&rsquo;s Platforms post after adding event-based triggering to my ML Pipeline. In 2022, I built a ML Pipeline to provide orchestration and MLOps for several models the Data Science team was ready to deploy to production. After an initial MVP, we wanted to move from static schedules to triggering on Snowflake table load events. There were a variety of conditions when we wanted to run training, inference, or both. For example, one time series model needed retraining monthly or when specific external datasets were updated. We could have written a rules engine and tried to describe all the conditions as rules. However, we saw the probable trajectory of increasingly complex conditions on the horizon. After thinking about it for a couple weeks and gathering input from various teams, I decided to implement the event filtering with a Data Science controlled code hook. This enables programatically deciding which phases to run on any given event. Reading Steve&rsquo;s thoughts on platforms a couple weeks later gave the satisfying feeling we had picked the right direction.</p>
<p>The orchestration code hook is stored in the ML model git repo and managed by the Data Science team. A custom Cloudwatch event of type <code>data_lake:table_loaded</code> triggers one of the ML Pipelines, and the Step Function will invoke the code hook to determine which phases to run. These phases include data prep, training, inference, and post-processing. The hook is executed in a Lambda function, and its interface is modeled after the Lambda handler. The <code>event</code> passed to the code hook is the unwrapped Eventbridge event. The <code>context</code> parameter contains properties like ML model name to allow code sharing across models without modification. Future iterations of this interface will add other methods to simplify consumer code like <code>get_snowflake_connection()</code>. After preparing the event and context objects, the Data Science team code hook file is imported via a dynamic module load as specified in the 
<a href="https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly" target="_blank" rel="noopener">python importlib docs</a>
. The result returned from the function call determines choice states later in the Step Function to select specific phases to run. We have started using this system for basic conditions, but the sky is the limit for the future. The determination can be based on data drift, upstream data quality, or any other future requirements.</p>
<p>Next week - 
<a href="/2023/wasm-for-platforms/">how WASM enables Platforms</a>
!</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
