<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Cynthia Coan</title>
    <description>The latest articles on Forem by Cynthia Coan (@mythra).</description>
    <link>https://forem.com/mythra</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51174%2F6018bec1-3c09-4a93-987b-14f1a1e91e89.png</url>
      <title>Forem: Cynthia Coan</title>
      <link>https://forem.com/mythra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mythra"/>
    <language>en</language>
    <item>
      <title>Dev-Loop a new approach to local development</title>
      <dc:creator>Cynthia Coan</dc:creator>
      <pubDate>Tue, 14 Apr 2020 17:14:45 +0000</pubDate>
      <link>https://forem.com/mythra/dev-loop-a-new-approach-to-local-development-29co</link>
      <guid>https://forem.com/mythra/dev-loop-a-new-approach-to-local-development-29co</guid>
      <description>&lt;p&gt;Dev-Loop is a new "localized task runner". It's built to help make a declarative, easy to use, command runner for all your local tasks. It's incremental to adopt so you don't need to do one massive change, It's piecemeal so if you like your Makefiles you can keep them, and it makes it possible to have install instructions that are two steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Another Tool Though?
&lt;/h2&gt;

&lt;p&gt;How many times have you started a new job, and heard: "So go through the setup steps, but be warned &lt;strong&gt;&lt;em&gt;they're probably horribly broken&lt;/em&gt;&lt;/strong&gt;". How many times have you looked at a new open source project, and just immediately said "nope" after looking at the install instructions?&lt;/p&gt;

&lt;p&gt;If you haven't looked at other projects recently. How many times have you been scared to make a build change because it would mean diving into a depth of Makefiles, or Shell Scripts that amass in &lt;code&gt;bin/&lt;/code&gt;? How many times have you struggled to remember a command that changed, or been disappointed by the fact you couldn't make a change because it'd break everyone's workflows?&lt;/p&gt;

&lt;p&gt;Chances are you've probably experienced one of these before in your time spent developing. Because as it turns out creating a really great local development experience is a &lt;em&gt;really hard&lt;/em&gt; task. Not only that, but it rarely gets prioritized. Everyone can build the code and knows the commands, why do we need to change it? Really the only time it gets noticed is when you're upgrading a tool, or bringing on someone new to the team.&lt;/p&gt;

&lt;p&gt;I remember once hearing from a team: "We'll put all the candidates in a room, and give them a local checkout of the repository. Whoever can figure out how to build our product gets hired." Jokingly to be sure, but it really shows how bad the state of local development is. &lt;/p&gt;

&lt;p&gt;Some languages make this easier: rust has &lt;code&gt;cargo&lt;/code&gt;, node has &lt;code&gt;npm&lt;/code&gt;, etc. These are great when &lt;em&gt;you're only dealing with that one language&lt;/em&gt;, however even for small projects these days it's becoming common to have multiple languages in play. For example a nodejs static site for documentation, and whatever your app is actually written in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Existing Solutions?
&lt;/h2&gt;

&lt;p&gt;There isn't really a solution that attempts to combat deficiencies, of using multiple languages effectively: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;There are tools to change how we build our code cross language (like &lt;a href="https://bazel.build"&gt;bazel&lt;/a&gt;, or &lt;a href="https://buck.build"&gt;buck&lt;/a&gt;) but these generally don't tackle things that aren't build related e.g. linting is still probably another step that's just some shell script, or you still have to install some local dependencies. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Makefiles/Shell Scripts &lt;em&gt;can be&lt;/em&gt; implemented well (but usually aren't). Not to mention whenever you want to do something like use docker to have the user not install something you end up with code like:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/mnt/src/"&lt;/span&gt; &lt;span class="nt"&gt;--workdir&lt;/span&gt; &lt;span class="s2"&gt;"/mnt/src/"&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"jdk-builder"&lt;/span&gt; openjdk:11 &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Failed to start container jdk-builder perhaps it's already running?"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$status_code&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; jdk-builder /bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"javac src/MyApp.java"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
  docker &lt;span class="nb"&gt;kill &lt;/span&gt;jdk-builder &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Failed to kill jdk-builder please kill manually."&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$status_code&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; jdk-builder /bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"jar -cvf MyJarFile.jar src/MyApp.class"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;...&lt;span class="o"&gt;}&lt;/span&gt;
docker &lt;span class="nb"&gt;kill &lt;/span&gt;jdk-builder &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Failed to kill jdk-builder, please kill it manually"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$status_code&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Tools like &lt;a href="https://taskfile.dev/#/"&gt;https://taskfile.dev/#/&lt;/a&gt; which simply move the builds into a more understandable format than a shell script.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be clear these are all great tools, they each do what they intend to do. However they're like a base piece. They give you a way of writing tasks, and running them. It's like writing in Assembly. Sure you can do anything but how you assemble it, and maintain it is critically important. Not to mention it doesn't make it easy for others to contribute unless they know how you're piecing that together.&lt;/p&gt;
&lt;h2&gt;
  
  
  Okay, That's Great, How Does Dev-Loop Solve These Problems?
&lt;/h2&gt;

&lt;p&gt;The best way to see what's happening is see it run right? Well let's take a look at it running:&lt;/p&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;



&lt;p&gt;So what's actually happening under the hood here? Why is this good?&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Declarative CLI
&lt;/h3&gt;

&lt;p&gt;The first thing you've probably noticed is the fact when I ran those commands they were declarative. Instead of running a specific tool, I simply said what I wanted to have happen. "Execute a build for Dev-Loop". This is really beneficial because it means if I want to change how my project is built I don't have to go teach a new command to anyone. They run the same thing. Even if directories change, I still run the same command.&lt;/p&gt;

&lt;p&gt;To make this easy to create we group things to do into "tasks". Each task should follow the Unix Tradition "Do one thing, and do it well". However one task is not the same as the others. There are three types of tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Command: Actually run a shell script.&lt;/li&gt;
&lt;li&gt;Oneof: Choose one of these tasks to run based off of a particular argument.&lt;/li&gt;
&lt;li&gt;Pipeline: Run these tasks in this specific order, only continuing if the previous one succeeded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three types provide the building blocks to build whatever it is you need, while making it easy to reuse. The task above actually uses all three of these types of tasks.&lt;/p&gt;

&lt;p&gt;First we use a "Oneof" task to provide a nice list of things you can build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;the top level build command&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oneof&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dl&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build the Dev-Loop binary&lt;/span&gt;
        &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-dl-debug&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dl-release&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build the Dev-Loop binary in release&lt;/span&gt;
        &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-dl-release&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build the documentation site&lt;/span&gt;
        &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs/"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have a series of options the user can choose from (which are discoverable with the "list" command you saw in the beginning of the video).&lt;/p&gt;

&lt;p&gt;A thing you might also notice here is we can pass arguments down to whatever we end up invoking. This helps us keep a declarative CLI over time, when we keep the arguments in config. Imagine if I wanted to change the arguments being sent to npm. Something contrived from: &lt;code&gt;run build&lt;/code&gt; to: &lt;code&gt;run build-and-compile-assets&lt;/code&gt;. With this I just change the config and push it up to git. All of a sudden as people checkout my patch they start using the new command without even noticing. Even if they for some reason need to reset to a much earlier point in time (before &lt;code&gt;build-and-compile-assets&lt;/code&gt; was introduced) they can still run &lt;strong&gt;&lt;em&gt;the same command.&lt;/em&gt;&lt;/strong&gt; You don't have to go teach someone a new command every time you wanna make a change to how your product builds.&lt;/p&gt;

&lt;p&gt;We executed build &lt;code&gt;dl&lt;/code&gt;, so let's take a peek at: &lt;code&gt;build-dl-debug&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-dl-debug&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pipeline&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rustc-build&lt;/span&gt;
        &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cargo-build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rename-bin&lt;/span&gt;
        &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rename&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./target/x86_64-unknown-linux-musl/debug/dev-loop&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./target/dl&lt;/span&gt;
    &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't all to different from &lt;code&gt;oneof&lt;/code&gt;, just we define what tasks we want to run in what order. So we build our project, and than move it to a more suitable location. One thing you may notice is the: &lt;code&gt;internal: true&lt;/code&gt; flag. This is a way of telling Dev-Loop "I don't want anyone to run this directly". This could either be because it accepts arguments that aren't guaranteed to stay the same, or it's just awkward to run (i.e. &lt;code&gt;dl exec build dl&lt;/code&gt; is much nicer than: &lt;code&gt;dl exec build-dl-debug&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Let's dig into something more juicy though, the actual &lt;code&gt;cargo-build&lt;/code&gt; command which actually runs our build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cargo-build&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;command&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
      &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cargo-build.sh&lt;/span&gt;
    &lt;span class="na"&gt;execution_needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rustc&lt;/span&gt;
    &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command type instead of specifying steps, or options instead tells Dev-Loop what script to run. In this case it says: "look for the file called &lt;code&gt;cargo-build.sh&lt;/code&gt; in the same directory".&lt;/p&gt;

&lt;p&gt;The other new stanza is: &lt;code&gt;execution_needs&lt;/code&gt;. What's going on with that? To explain this in it's entirety we need to talk about Executors. Which also are the same reason your install instruction can be two steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstracting Away the Host System
&lt;/h2&gt;

&lt;p&gt;Dev-Loop provides a way of "abstracting away" the host system. Dev-Loop realizes that host machines have different commands, behaviors, and installations. I mean even the "date" command acts differently between Mac, and Linux. Docker can solve this, however as we saw above it has a tendency of being very hard to manage docker container lifetimes, and generally isn't the most pleasant thing to do. There are a couple different ways to solve this too (maybe you run everything over ssh on a remote host?), but every single previous solution requires your task knowing where it's running. Not to mention keeping state between tasks "has this container already started up? if so don't try to stand it up again.", "Am I the last task using this container? Should I kill this container?".&lt;/p&gt;

&lt;p&gt;Luckily because Dev-Loop already decides what tasks to execute when a user types a command. It is also in the perfect place to answer these sort of questions, and also be knowledgeable about where tasks should run. This is what the &lt;code&gt;execution_needs&lt;/code&gt; stanza is for in the previous configuration. It's saying to Dev-Loop: "hey please run me in an environment with 'rustc', I don't care about the version".&lt;/p&gt;

&lt;p&gt;Next Dev-Loop is told about what types of runtime environments there are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;export_env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RUST_BACKTRACE'&lt;/span&gt;
      &lt;span class="na"&gt;extra_mounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scratch/rust-git-cache/:/root/.cargo/git/,scratch/rust-registry-cache/:/root/.cargo/registry/'&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clux/muslrust:1.41.0-stable'&lt;/span&gt;
      &lt;span class="na"&gt;name_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rustc-musl-'&lt;/span&gt;
    &lt;span class="na"&gt;provides&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4.0.0'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rustc&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.40.0'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node:12.14'&lt;/span&gt;
      &lt;span class="na"&gt;name_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nodejs-'&lt;/span&gt;
    &lt;span class="na"&gt;provides&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;12.14.0'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4.0.0'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This list of possible "executors" tell Dev-Loop where it could run. In this case there's only one executor that provides "rustc". So Dev-Loop knows when it wants to run the "cargo-build" task it needs to stand up that specific docker container if it hasn't been already. Than at the end Dev-Loop knows the container was stood up, so it shuts it down.&lt;/p&gt;

&lt;p&gt;No longer does the task itself have to worry about where it's run. It just knows it's executing in the correct environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  But Wait, Don't I Need To Get The File Out Of The Docker Container?
&lt;/h3&gt;

&lt;p&gt;Well yes, and no. By default Dev-Loop is going to mount two directories. First the root of the repository, and second is the temporary directory of the system. These are the most common directories people will need to access.&lt;/p&gt;

&lt;p&gt;This means the files you are creating (like build output) are automatically synchronized to the file system. Again this is making the actual execution environment as invisible as possible. Allowing them to compose together with no problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay so is that it?
&lt;/h2&gt;

&lt;p&gt;Yes, basically! These may seem like two small things, but regardless of how small they may seem, they change how you approach local development, both of which make this possible. If you take advantage of executors, you as a maintainer don't have to troubleshoot what version of things people have installed (since they're just in docker) and, as a consumer, you don't have to try and figure out what versions things are used. You just run a command and, as long as you have docker/dev-loop installed, you're off to the races.&lt;/p&gt;

&lt;p&gt;If you use tasks you have a much easier way of discovering commands. Not to mention, you can easily change the actual underlying implementations of the commands that are being run.&lt;/p&gt;

&lt;p&gt;Both of these together can be used to create a whole new, easy-to-use developer experience. If you want to learn more, you can go through &lt;a href="https://dev-loop.kungfury.io/docs/walkthrough/installing"&gt;the walkthrough&lt;/a&gt;, which is meant to help you get familiar with using dev-loop. If you just want to try using it, why not try it out in the &lt;a href="https://github.com/SecurityInsanity/dev-loop"&gt;dev-loop repo&lt;/a&gt; itself?&lt;/p&gt;

&lt;p&gt;Regardless of what you do, I hope that this has either given you ideas of how you can improve your local dev experience, or provide a tool that does actually improve the experience for you.&lt;/p&gt;

</description>
      <category>tools</category>
    </item>
    <item>
      <title>Showdev: Coworker - A New Job Queue for the JVM</title>
      <dc:creator>Cynthia Coan</dc:creator>
      <pubDate>Tue, 25 Sep 2018 00:38:18 +0000</pubDate>
      <link>https://forem.com/mythra/showdev-coworker---a-new-job-queue-for-the-jvm-1dnn</link>
      <guid>https://forem.com/mythra/showdev-coworker---a-new-job-queue-for-the-jvm-1dnn</guid>
      <description>&lt;p&gt;If you've ever written a ruby on rails app you'll know a very common pattern is to have a Job Runner, like &lt;a href="https://github.com/collectiveidea/delayed_job" rel="noopener noreferrer"&gt;Delayed Jobs&lt;/a&gt;, &lt;a href="https://github.com/mperham/sidekiq" rel="noopener noreferrer"&gt;Sidekiq&lt;/a&gt;, or a similar framework. However, you may be surprised there aren't that many in the Java Ecosystem! In fact, one of the only standard solutions was &lt;a href="https://github.com/gresrun/jesque" rel="noopener noreferrer"&gt;Jesque&lt;/a&gt;, until now.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Mythra" rel="noopener noreferrer"&gt;
        Mythra
      &lt;/a&gt; / &lt;a href="https://github.com/Mythra/Coworker" rel="noopener noreferrer"&gt;
        Coworker
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A reasonably-good delayed job queue for the JVM.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ARCHIVAL NOTICE#&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Coworker was originally open-sourced from an internal project to showcase the ideas we were testing that worked so well for us. It's fallen significantly out of date, and because of when it was written documentation on Kotlin Coroutines weren't great. As a result I've noticed a few bugs/inneffecienes that exist. I plan a rewrite of these at some point, but haven't really had the time as of yet. If you have any questions though feel free to reach out, and ask about how the SQL/Code works (the SQL is the same as internal and can be trusted to be the most effecient).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Coworker&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Coworker is a delayed work runner built for JVM Based languages, written in
kotlin. Coworker started off as an experiment as bringin coroutine ideas
to background work. Allowing you to work on something else if you're say
waiting for an external system.&lt;/p&gt;
&lt;p&gt;Specifically Coworker introduces…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Mythra/Coworker" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;For those of you who haven't dealt with a job queue before, you may be curious why one is needed? What problems does it solve?&lt;/p&gt;

&lt;p&gt;Imagine you're developing a web application, and you are needing to do something that can take a long time. For example, you make a call out to another application that may fail, you may want to retry, and altogether it ends up taking a couple minutes. You may start worrying about HTTP Timeouts here, where your server takes too long to respond. If so it might make sense to refactor it out into a job. Then give a user a Job ID they can check for completion status. That way you don't have HTTP Timeouts, and calls still happen in the background. Some examples of these longer tasks that should be run in the background include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sending newsletters to massive amounts of people&lt;/li&gt;
&lt;li&gt;image resizing&lt;/li&gt;
&lt;li&gt;batch importing&lt;/li&gt;
&lt;li&gt;checking for spam&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm sure you can imagine more for your specific application. However, if you go looking for job queues in Java you'll find there aren't many solutions! Many people recommend the following things from what I've seen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Building your own&lt;/em&gt; - While this certainly works it does mean you have to build and maintain a solution yourself.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Using a local thread pool&lt;/em&gt; - While this certainly works it can present a problem if a web server goes down while having a queue of jobs it hasn't worked yet, leading to a loss of jobs. Not to mention this makes it hard to check the status of a job if you're behind a load balancer that distributes packets across a series of hosts.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Using a Job Scheduler&lt;/em&gt; - Some others recommend using a job scheduler like &lt;a href="http://www.quartz-scheduler.org/" rel="noopener noreferrer"&gt;Quartz&lt;/a&gt;. While quartz is great, using it as a job queue is specifically marked as an anti-pattern, and for good reason. Running hundreds of ad-hoc image resizing jobs at once with Quartz performs very slowly because Quartz simply isn't built for that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Coworker to the Rescue
&lt;/h2&gt;

&lt;p&gt;Coworker joins the space of an actual job queue in java, like &lt;a href="https://github.com/gresrun/jesque" rel="noopener noreferrer"&gt;Jesque&lt;/a&gt;. However, Coworker has been built from the ground up to be more efficient in job processing for real-life jobs. By taking ideas built in the fire of production from the ruby library &lt;a href="https://github.com/instructure/inst-jobs" rel="noopener noreferrer"&gt;InstJobs&lt;/a&gt;, and building on ideas from local execution like Fibers. With these features combined &lt;strong&gt;&lt;em&gt;we have the power&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-clownfront-distribution.insops.net%2Fimages%2FIHaveThePower.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-clownfront-distribution.insops.net%2Fimages%2FIHaveThePower.gif" alt="He Man I Have the Power"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where that power is executing hundreds of jobs a second on a single box. Specifically, some of the elevator pitch features of coworker include: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self-Healing: Stick your job nodes in an AutoScaling group, and don't worry about it! Jobs will automatically be rescheduled if an underlying node dies.&lt;/li&gt;
&lt;li&gt;Failed Work Table: Allowing you to track the total number of failed jobs.&lt;/li&gt;
&lt;li&gt;N-Strands: Now you can limit your jobs based on ad-hoc tags, so you can ensure that one really big customer doesn't starve your entire job queue.&lt;/li&gt;
&lt;li&gt;Yielding: That's right the famous key-word used all the time in Fibers/Coroutines is here for your jobs too. Now your jobs have the ability to yield to higher priority work if it comes in the queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We bring you all these features (and more), without compromising the most important thing. Being nice to your database. We've implemented multiple safeguards in order to make sure we take up as little room in your database as possible. We know you might be sharing your jobs database, with your customer's database, and we want to not slow you down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Testing
&lt;/h3&gt;

&lt;p&gt;If you look at the README for Coworker, one of the first things you'll see is our area around performance testing. Specifically, you'll see a graph that looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FSecurityInsanity%2FCoworker%2Fmaster%2Fbench%2Fcoworker-timings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FSecurityInsanity%2FCoworker%2Fmaster%2Fbench%2Fcoworker-timings.png" alt="Graph of Coworker Performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where Coworker is the line labeled: "1", and Jesque is the line labeled: "0". Now you're probably thinking to yourself "a performance test graph on GitHub, this is probably built to be shown in a light where Coworker will pretty much always win". This is technically true, but only because Coworker is built to run real-life job scenarios much faster!&lt;/p&gt;

&lt;p&gt;Specifically, this performance test is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queue 200 jobs:

&lt;ul&gt;
&lt;li&gt;100 jobs just echo to standard out and complete immediately after. These are meant to map to "quick running jobs" or jobs that you queue that do little work, and then exit out. I'm sure you'll have some of these in your application.&lt;/li&gt;
&lt;li&gt;100 jobs that make an HTTP call to google.com, and wait a minute before exiting. This is meant to represent long-running jobs or jobs where you want to wait a bit for it to complete.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Count how many seconds elapse before all jobs are marked as completed.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Although this isn't the perfect representation (and we could never perfectly mirror your application) it is a much better representation of your actual workload, as opposed to just queueing 100,000 jobs that exit out immediately and seeing how long it takes to complete, since this is not the common use case for jobs. (However, it should be noted: if your job queue is like this: Jesque is much faster than Coworker in this specific test. This is because Jesque uses Redis as its store which can query for jobs much faster than SQL ever could.)&lt;/p&gt;

&lt;p&gt;If you notice in this case Coworker completes in 70 seconds! Compared to Jesque's near 600 second job completion time. So &lt;em&gt;why&lt;/em&gt; is Coworker so much faster here? Well, it's simple! Yield'ing! Coworker has the ability to yield for the HTTP Call job, whereas Jesque does not. So Jesque has all 10 worker threads blocked. Waiting for this long job to complete. Meanwhile, Coworker can yield to other work allowing more work to actually be worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  NStrands
&lt;/h3&gt;

&lt;p&gt;The next really big thing that Coworker brings to the table is NStrands! NStrands, as mentioned above, are a really great way of ensuring your job pool doesn't get starved in various scenarios. If you've ever run a large app before for a business you'll know some clients are bigger than others. This can lead to a problem if you use a lot of delayed jobs! Specifically, your bigger customers can end up starving your work pool. By that I mean the big customers are queueing &lt;strong&gt;&lt;em&gt;so many jobs&lt;/em&gt;&lt;/strong&gt; that all the workers end up doing the work for &lt;strong&gt;&lt;em&gt;that one client&lt;/em&gt;&lt;/strong&gt; while everyone else has to wait for their work to be completed.&lt;/p&gt;

&lt;p&gt;The big customer probably won't notice they're taking up all your resources. After all, they're just using the system. However, your lower use customers certainly will notice as jobs take longer than before to complete.&lt;/p&gt;

&lt;p&gt;This doesn't always have to be a customer thing though. Perhaps you have one job that's very very common, and gets queued &lt;em&gt;a lot&lt;/em&gt;. You could run into a similar problem with this job! Where there are so many instances of this job to complete, they also just starve your workers, not allowing much useful work to actually be done.&lt;/p&gt;

&lt;p&gt;Using the client example above we could whip up an NStrand rule that looks like this: &lt;code&gt;account:.* -&amp;gt; 5&lt;/code&gt;. Next what we'll do is, every time we queue a job we'll "Tag" it (or set its strand) to the value of: &lt;code&gt;account:${account_id_that_queued_this_job}&lt;/code&gt;. So for example, if you belong to account 1, and you queue a job it'll be tagged: &lt;code&gt;account:1&lt;/code&gt;; if you belong to account 2, it will be tagged: &lt;code&gt;account:2&lt;/code&gt;, and so on. &lt;/p&gt;

&lt;p&gt;Next, in our hypothetical scenario let's say account "1" is our big client, and account "2", "3", and "4" are our small clients. Let's say we queue a job everytime a user visits a page, to go update our analytics in the background. Well since account "1" is so much bigger we'll say they queue 10 jobs, and account "2"/"3"/"4" each queue 1 job each. We have one job box with 10 worker threads. How will Coworker schedule this work?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-clownfront-distribution.insops.net%2Fimages%2Fthonk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-clownfront-distribution.insops.net%2Fimages%2Fthonk.png" alt="Thinking Face"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Coworker will actually schedule 8 jobs at once, and leave 5 in the queue! Specifically, it will run 5 "account:1" jobs, and each of the account "2"/"3"/"4" jobs. Because you've set a rule that you only want 5 jobs of a particular account running at a specific time! Then once one of the "account:1" jobs finishes it will see only 4 jobs are working, and then queue the next "account:1" job. This way your big account didn't starve out your one singular worker.&lt;/p&gt;

&lt;h3&gt;
  
  
  LISTEN/NOTIFY
&lt;/h3&gt;

&lt;p&gt;Lastly, we take advantage of lesser used features of PostgreSQL in order to execute jobs faster (and with less load) than most SQL based queues. Specifically, it uses &lt;a href="https://www.postgresql.org/docs/current/static/libpq-notify.html" rel="noopener noreferrer"&gt;LISTEN/NOTIFY&lt;/a&gt;. Which allows us to validate when new work has entered the queue &lt;em&gt;without needing to poll for it&lt;/em&gt;. (It should be noted however that LISTEN/NOTIFY is completely ephemeral. We still need to scan the table occasionally for work that hasn't been locked, and we don't know about. However, we can do it a lot &lt;em&gt;less&lt;/em&gt; often).&lt;/p&gt;

&lt;p&gt;However, we also worry about building up the DB Memory! Because although LISTEN/NOTIFY lives outside of a "Table" so we don't slow down queries when asking for any notifications that have arrived, these notifications do build up in the databases memory over time! So in order to fix this, we run a sidecar thread that continuously checks for notifications in order to get them out of the DBs memory, and onto the worker node. Where we then push them into a channel. So that way the delayed worker can check them whenever it gets to it, and our database doesn't just use all of its memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  And Much More...
&lt;/h3&gt;

&lt;p&gt;Coworker has some other features that we haven't talked about, like being able to queue &lt;a href="https://coworker.kungfury.io/#/insert-work?id=using-handle-asynchronously" rel="noopener noreferrer"&gt;static functors to run in the background&lt;/a&gt;, how it doesn't lock you into a specific web framework, and more. However, we've left that as an exercise for you the reader. If you're building a web app in Kotlin/Java, and you’ve needed a delayed job runner, Coworker might be for you!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>kotlin</category>
      <category>java</category>
    </item>
  </channel>
</rss>
