Code Monkey home page Code Monkey logo

acidic_job's Introduction

Hi there 👋

I’m Stephen. I am an American expat living in Berlin with my wife and 2 dogs—Tuck 🐶 and Ruby 🐕 (weirdly both emoji are pretty accurate depictions). I am someone who sweats the details, someone that would rather design in CodePen than Figma, someone who can learn both deeply and quickly because they aren't afraid to read obscure low-level source code or keep tabs on upcoming HTML, CSS, and JS features, and someone that enjoys sharing problems, solutions, tips and tricks with others.

I'm a quick learner

At this point two years ago, I had never read or written a line of C, knew nearly nothing about SQLite, and had only contributed one small documentation fix to Rails core. Fast forward to today, I am now a top-500 Rails core contributor. I have written multiple PRs to the sqlite3-ruby gem (I'm now a top-5 contributor), including a few in C. I have also now read large swathes of the SQLite C source code, and I am in the process of writing a lexer and parser for SQLite by translating the entagled C code into Ruby. I am now recognized as one of the the experts in SQLite in the Ruby community.

Similarly, when I got my first job in tech after leaving graduate school in Classical Studies, I had never written a line of Ruby and knew nothing about Rails. Nonetheless, within 6 months of starting I was promoted from junior to mid-level Rails developer. In my second job, I was again promoted after 6 months in the role. This time, though, I was promoted to an engineering management role, and I have spent the last 4 years growing our team from 6 on-site, full-stack engineers maintaining one product to a department of 45+ remote engineers who have built and now maintain 4 products.

My key to learning new topics, tools, or technologies is to dive in head first and build something. I am a hands-on learner. Whether it is things for work or things I am simply interested in, I learn by doing. Being such a hands-on "manager-of-one" has granted me essentially all of my opportunities. I got into tech by building tools to be a more effective and efficient scholar. I got my first job from building Alfred workflows. And I now have a reputation in the Ruby community for my work on SQLite because I built various applications using SQLite and numerous tools on top of SQLite.

I'm a minimalist perfectionist

I like to focus on leverage—how you I produce the absolute best possible interface, component, DX with the absolute minimal bloat. I think of myself as a minimalist perfectionists.

I recently tried to implement the Superlist inbox page in Tailwind CSS, which I could only do by eye as the source code is not available. I then spent the next two weeks before leaving for my wedding working to build a Rails clone using cutting-egde browser features (e.g. popovers and anchor positioning) and morphing. I streamed the whole process. It isn't perfect, I am still not satisfied with it, but I also didn't let that stop me from iterating, shipping, and sharing.

I also love to explore UX patterns to try and find an optimal balance between beauty, usability, and simplicity. As one example, I experimented with two different approaches to building a multiple file input in an edit form. In one version, I split the already saved files from the newly attached files and displayed them in two separate lists. In the other, I combined them into a single list but varied their styling. I believe that the second approach is more intuitive and user-friendly, but I am not sure. I would love to hear your thoughts. For one other example, I tried a few different approaches to an avatar component. I started off with a simple screen name text input that generates a deterministic avatar from Boring avatars. I then tried using a generated avatar from avvvatars along with a file upload button. Finally, I allowed the user to choose if they wanted a shape or initial-based avatar or upload a custom image. Polish isn't simply how it looks statically, but how it works and feels to use.

I want to delight

Where I am from in Louisiana, we have the concept of lagniappe, or a little something extra. I want the things I build to have lagniappe. When I was exploring inline text editing, I wanted to keep things simple by simply stying regular form inputs to either look like static content or editable fields depending on focus, but I also wanted that transition between states to be delightful. I've experimented with both single line and multi-line inputs, which each utilizing slight Tailwind animations. In fact, my very first larger project was a CSS library to provide a collection of icons that you could visually toggle between, like an up-chevron morphing into a down-chevron.

I will spend hours tweaking slides, even just for our company's bi-weekly Townhalls, where I have ended up incidentally defining much of the aesthetic of our slide deck. In the slide deck for my most recent conference talk, I spent days scouring the internet, working with GPTs, and experimenting in Sketch just to create this one image of a red ruby and a blue feather that I only use for a few seconds.

I also care deeply about the polish and delight in the code interfaces I build. I had been experimenting with Tachyons for a bit and liked atomic CSS, but I found their terse class names to be a bit too terse. I strive to strike a similar balance in the code that I write, especially that code that is meant to be consumed by other developers. I want my code to be a joy to use, delightful to read, and a pleasure to maintain.

Finally, I look to delight people through speaking and sharing. I will give talks at no less than 4 conferences this year. I have also been a guest on various podcasts, and I am writing regularly on various topics in web development. This year I also started a Youtube channel where I have done a two week sprint building the Superlist clone. When I look into the future of my career, I see myself continuing to share and teach, whether that be through speaking, writing, or mentoring.

acidic_job's People

Contributors

djfpaagman avatar fractaledmind avatar julianrubisch avatar petergoldstein avatar vtinman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

acidic_job's Issues

Set `$!` when deserializing an exception

In the ExceptionSerializer, we serialize and deserialize exceptions stored in Run records. Some exceptions (like the ActionView::Template::Error) expect the global sigil $! to be defined, as if the exception was raised naturally. By having this $! sigil undefined, we get errors when trying to deserialize exceptions.

Missing deployment / runtime related documentation

I couldn't find any documentation on deployment and runtime related issues such as: how to start AJ, how it manages queues, monitoring, etc. Is it possible I missed the details somewhere?

If not, I think the lack of this information would stop people from even trying AJ, which is a shame as it's a very, very interesting project that I would have loved to use in my application.

Add a find-or-create macro

Imagine a scenario like this. We have a workflow where we need the author of a Slack message. Maybe a webhook has already processed and created our internal database representation, maybe it hasn't. So, in our job, we need to

  1. try and find the internal record
  2. if we don't have it, make an API call to fetch the data
  3. if the API read succeeds, create the internal record
  4. find the newly created internal record and set the variable

for example:

def find_author
  self.slack_author = event.installation.profiles.find_by(uid: @user_uid)
end

def fetch_unknown_author_from_slack
  return [] if @slack_author

  api_response = event.installation.client.user(uid: @user_uid)
  if api_response[:ok]
    [Slack::ProcessProfileSlackJob.with(event.installation, api_response[:user])]
  else
    raise DoNotRetryJob
  end
end

def ensure_author_known
   self.slack_author ||= event.installation.profiles.find_by(uid: @user_uid)
  raise DoNotRetryJob and return if @slack_author.nil?
end

Should this be something that the DSL makes more straight-forward?

Using a Custom Idempotent Key AND an iterable step = run once only

Using a Custom Idempotent Key and an iterable step (using a for_each:), results in the job queue not running any jobs after the first job is completed. The AcidicJob will only run the first job, including all its steps, but all others jobs enqueued after this are only initialized but never run. In effect, this Custom Indempotent queue is stuck.

Sample code :

class MyJob < ApplicationJob
  # custom idempotent key so that all transactions with the distant server are sequential
  acidic_by do
    distant_server_config = arguments.first   # First parameter to the .perform method
    "DistantServer-" + distant_server_config.api_key
  end

  def perform(distant_server_config)
    @distant_server_config = distant_server_config

    with_acidic_workflow persisting: { collection: [1, 2, 3, 4, 5] } do |workflow|
      workflow.step :step1, for_each: :collection
      workflow.step :step2
    end

  def step1(num)
    p "in step1 with " + num.to_s
    true
  end

  def step2
    p "in step2"
    true
  end
end

Then do
dist_server.api_key = "abcdef"
MyJob.perform_later(dist_server)
sleep 2
MyJob.perform_later(dist_server)

Result
step1 is displayed 5 times, followed by step2
(the second job is never executed)

Expected result
step1 is displayed 5 times, followed by step2
step1 is displayed 5 times, followed by step2

How to reset the jobs?

Hi,
First day with AcidicJob.

I've created a Custom Idempotency Key and then tried to add a collection to one of the steps. I may have had a syntax error or something in the step or workflow, I'm not sure. But now it wont run any steps in this job, nor any more job (on this idempotency key). How can I reset the job or the whole gem?

I tried to AcidicJobRuns.destroy_all in the console but it fails with uninitialized constant AcidicJobRuns (NameError) Actually AcidicJobRuns.all fails with the same error.

Stuck :/

Track number of executions in Run record

It is useful to know if a Run is in a retry or on the first run. For example, imagine a step method that makes an API call to create an external resource. That step method could fail during the "execution" stage such that the API call was successful, but the run errors while trying to close the transaction or update the Run record. In this case, the job will be retried and re-run this step method.

We can add another API call as a guard to check if the resource already exists in the external system, but on the first run of the job, this is guaranteed not to be the case (presuming the job is setup properly to be idempotent). We could save the API call on the first run and only check in subsequent runs, if we knew whether the run was a first run or not.

Handle case when error occurs trying to write an attr_accessor value

Imagine a workflow step method that makes an API call to create a record in an external system:

def create_external_record
  self.external_record = ExternalClient.create(...)
end

Now, imagine that we get a timeout error when AcidicJob is trying to write the value to the Run#attr_accessors field.

In this case, the record is created in the external system, but we don't have a persisted copy of that record on our end.

Rename Key to Run

After exploring the problem space more deeply in my RubyConf talk, I prefer the semantics of AcidicJob::Run

Allow `for_each` step argument to accept a method name

Currently for_each can only provide the key name for a key present in the persisting hash. This means that steps can only iterate over data present before the workflow begins. We need to be able to allow a workflow to iterate over data generated within the workflow itself. Consider, for example, a workflow job that traverses and processes the paginated responses from an API endpoint. We can have a step method to process each page, but we would need an earlier step method to make the pagination API requests to feed the iteration.

Handle duplicate workflow step names

Imagine having a job like this:

with_acidic_workflow persisting: { } do |workflow|
  workflow.step :no_op, awaits: [PreprocessingJob.new]
  workflow.step :do_thing
  workflow.step :no_op, awaits: :post_process_if_necessary
end

Currently, workflow steps with the same name will conflict and only one will be set in the Run#workflow hash. This is a foot-gun. We don't like foot-guns. We should resolve

Store errors with more context

Currently, we store any error object in the error_object field. This doesn't provide any context on which step caused the error, or which execution the error occurred in. We should track and store both bits of context.

How should we handle workflows changing underneath job runs

When working locally (or even in production), it is possible to run a job (r1), have it error and stop on some step, update the code of the job changing the shape of the workflow, and then retry the job (r2). There are a number of pain-states that can occur in this situation:

  • if persisted attributes were added, and the code to persist them was added to steps that occur before the run's recovery_point, that attribute will never get persisted

Uninitialized constant AcidicJob::ActiveKiq

After following the prescribed setup, I keep running into the issue of Uninitialized constant AcidicJob::ActiveKiq when dealing with pure Sidekiq jobs.

I have created a small rails app for an example:
You can clone it: https://github.com/RomanTurner/sidekiq-practice
After cloning, you can run rails db:setup. This will create/migrate/seed for the database.

To find the error you can either travel to the route at localhost:3000/students/:id/example
or in the rails console run: Students::PureSidekiqJob.perform_async(:student_id)

class Students::PureSidekiqJob < AcidicJob::ActiveKiq
  include Sidekiq::Job

  def perform(student_id)
    @student = Student.find(student_id)

    with_acidic_workflow do |workflow|
      workflow.step :student_says_hello
    end
  end

  def student_says_hello
    p "Student #{@student.first_name} #{@student.last_name} says 'Hello!'"
  end
end

Ensure awaited job failures trigger the callback if returned from the dead set

We need to manually check (and hopefully then write an automated test) to ensure that if a job called as an await job for a workflow step fails and ends up in the Dead Set, and is then manually re-enqueued and succeeds, that the workflow callback successfully fires and the next step in the workflow is kicked off

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.