<?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: Alexander Shagov</title>
    <description>The latest articles on Forem by Alexander Shagov (@alexander_shagov).</description>
    <link>https://forem.com/alexander_shagov</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%2F1997001%2F63bf5fec-70b7-4b7b-9080-e1c41326c365.jpeg</url>
      <title>Forem: Alexander Shagov</title>
      <link>https://forem.com/alexander_shagov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alexander_shagov"/>
    <language>en</language>
    <item>
      <title>How LLM users accidentally do TDD without realizing it</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Sat, 10 May 2025 07:24:54 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/how-llm-users-accidentally-do-tdd-without-realizing-it-3dc</link>
      <guid>https://forem.com/alexander_shagov/how-llm-users-accidentally-do-tdd-without-realizing-it-3dc</guid>
      <description>&lt;p&gt;Most developers using LLM tools today are unknowingly following a loose version of test-driven development (TDD).&lt;/p&gt;

&lt;h3&gt;
  
  
  1. TDD and LLM prompting work surprisingly similar
&lt;/h3&gt;

&lt;h4&gt;
  
  
  a) Start with failure
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;TDD: You define an interface and write a failing test first. Now you know what needs fixing.&lt;/li&gt;
&lt;li&gt;LLMs: You write a vague prompt, get bad results, and think "I need to explain this better."
Same basic concept.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  b) Make it work
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;TDD: You write code until the test passes.&lt;/li&gt;
&lt;li&gt;LLMs: You tweak the prompt, add details, and retry until you get good output.
Same iterative approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  c) Clean it up
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;TDD: "Okay, now let's clean this up."&lt;/li&gt;
&lt;li&gt;LLMs: "This works but looks messy - needs polishing."
Same final step.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Why developers end up doing TDD-like work with LLMs
&lt;/h3&gt;

&lt;p&gt;Many developers claim to hate TDD ("too much overhead!") but end up doing something similar because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They have to think through requirements first (or the AI won't help)&lt;/li&gt;
&lt;li&gt;They iterate repeatedly (just with prompts instead of tests)&lt;/li&gt;
&lt;li&gt;They refine the AI's output (which is rarely perfect on first try)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. LLMs reveal who actually understands coding
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Beginners: Ask AI → Copy → Paste → Breaks → Gets stuck&lt;/li&gt;
&lt;li&gt;Experienced devs: Plan → Experiment → Debug → Verify results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mirrors TDD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weak developers write tests that don't actually help&lt;/li&gt;
&lt;li&gt;Strong developers write tests that improve code and serve as documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The irony
&lt;/h3&gt;

&lt;p&gt;If you avoid TDD but use LLMs... surprise! You're basically doing TDD-ish style of work without calling it that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TDD is structured (test → fail → code → refine)&lt;/li&gt;
&lt;li&gt;LLM prompting is more ad-hoc (ask → adjust → accept → maybe improve later)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What this shows us
&lt;/h3&gt;

&lt;p&gt;Skilled developers use LLMs like TDD - they plan, test, and refine.&lt;br&gt;
Junior developers use LLMs like supercharged Stack Overflow - copy, paste, and pray.&lt;/p&gt;

&lt;p&gt;In the end, any tool just &lt;strong&gt;amplifies&lt;/strong&gt; your existing thought process.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>tdd</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Configuring your own deep research tool (Using Nix Flakes)</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Mon, 28 Apr 2025 15:48:44 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/configuring-your-own-deep-research-tool-using-nix-flakes-56ah</link>
      <guid>https://forem.com/alexander_shagov/configuring-your-own-deep-research-tool-using-nix-flakes-56ah</guid>
      <description>&lt;p&gt;So, you've come across deep search tools and are wondering if you can build your own. Well, you certainly can! Building one yourself might involve tasks like scraping web URLs, implementing your own RAG (Retrieval-Augmented Generation) system to find relevant information, and then summarizing it using an agent loop.&lt;/p&gt;

&lt;p&gt;Luckily, the open-source community has already created tools that handle many of these steps. The purpose of this article is to demonstrate how easily we can spin up the necessary environment for such a tool using &lt;strong&gt;Nix&lt;/strong&gt;. We'll focus not only on running the service, but also on development environment setup, making it easy to modify the tool if needed.&lt;/p&gt;

&lt;p&gt;For demonstration purposes, I'll be using the &lt;code&gt;gpt-researcher&lt;/code&gt; tool.&lt;br&gt;
Github link: &lt;a href="https://github.com/assafelovic/gpt-researcher" rel="noopener noreferrer"&gt;https://github.com/assafelovic/gpt-researcher&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The basic setup for almost any Python project often feels trivial: install Python, install &lt;code&gt;requirements.txt&lt;/code&gt;, and you're good to go. However, if you manage tens of these projects on your machine, you'll quickly realize you not only need to isolate your &lt;em&gt;project&lt;/em&gt; environments (which &lt;code&gt;.venv&lt;/code&gt; solves) but ideally also isolate the &lt;em&gt;Python installations themselves&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Welcome Nix flakes!&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Use Nix for Your Development Environment?
&lt;/h3&gt;

&lt;p&gt;Nix is a powerful package manager that helps ensure your development environment is reproducible and isolated. By defining your dependencies (like Python and other tools) in a Nix flake, anyone (including your future self!) can recreate the exact same environment with a single command, avoiding "it works on my machine" problems.&lt;/p&gt;

&lt;p&gt;To get a better grasp of what Nix is, you can check out &lt;a href="https://dev.to/alexander_shagov/why-use-nix-even-on-macos-786"&gt;my beginner's article&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Nix flake
&lt;/h3&gt;

&lt;p&gt;Once you pulled the repo, the only thing you need to do (besides .env configuration with your own API keys), is to setup the dev environment using a nix flake. Here's what my file looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nixpkgs"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:numtide/flake-utils"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flake-utils&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;eachDefaultSystem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;allowUnfree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;devShells&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nv"&gt;git&lt;/span&gt;

            &lt;span class="nv"&gt;python311&lt;/span&gt;
            &lt;span class="nv"&gt;python311Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;pip&lt;/span&gt;
            &lt;span class="nv"&gt;python311Packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;virtualenv&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;

          &lt;span class="nv"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # Create and activate virtual environment if it doesn't exist&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            VENV=.venv&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            REQUIREMENTS_HASH=".venv/.requirements.hash"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;            # Calculate new hash of requirements file&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            NEW_HASH=""&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            if [ -f "requirements.txt" ]; then&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              NEW_HASH=$(sha256sum requirements.txt)&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            fi&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;            if [ ! -d "$VENV" ]; then&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              echo "Creating virtual environment..."&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              python -m venv "$VENV"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              source "$VENV/bin/activate"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;              # Initial pip upgrade and requirements installation&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              pip install --upgrade pip&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              if [ -f "requirements.txt" ]; then&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                echo "Installing requirements from requirements.txt..."&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                pip install -r requirements.txt&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                echo "$NEW_HASH" &amp;gt; "$REQUIREMENTS_HASH"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              fi&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              echo "Virtual environment setup complete!"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            else&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              source "$VENV/bin/activate"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;              # Check if requirements have changed&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              if [ -f "requirements.txt" ]; then&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                if [ ! -f "$REQUIREMENTS_HASH" ] || [ "$NEW_HASH" != "$(cat $REQUIREMENTS_HASH)" ]; then&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                  echo "Requirements have changed. Updating..."&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                  pip install --upgrade pip&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                  pip install -r requirements.txt&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                  echo "$NEW_HASH" &amp;gt; "$REQUIREMENTS_HASH"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;                fi&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              fi&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            fi&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;            # Print Python version&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            echo "Using Python: $(python --version)"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the Environment
&lt;/h3&gt;

&lt;p&gt;Once you have the &lt;code&gt;flake.nix&lt;/code&gt; file in your project's root directory (assuming Nix with flakes is installed), the only command you need to run is &lt;code&gt;nix develop&lt;/code&gt;. Running this command will drop you into an isolated development shell.&lt;/p&gt;

&lt;p&gt;Within this shell, you'll have access to the tools defined in the flake, such as the specific Python version requested. Additionally, the custom &lt;code&gt;shellHook&lt;/code&gt; I included in the flake automates the management of your Python dependencies. This hook automatically creates and activates a standard Python virtual environment (&lt;code&gt;.venv&lt;/code&gt;). More importantly, it checks your &lt;code&gt;requirements.txt&lt;/code&gt; file and automatically installs or updates your Python packages using &lt;code&gt;pip&lt;/code&gt; whenever the contents of &lt;code&gt;requirements.txt&lt;/code&gt; change. And everything isolated for this specific repo without any leakeges to the outside world!&lt;/p&gt;

&lt;p&gt;The result is a fully prepared, self-contained development environment that keeps your project dependencies neatly isolated from the rest of your system, and also isolates the core tools like the Python interpreter itself – a key advantage over managing global installations or relying solely on &lt;code&gt;.venv&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not docker?
&lt;/h3&gt;

&lt;p&gt;Now, why not Docker? While Docker is awesome for &lt;strong&gt;deploying&lt;/strong&gt; things, for &lt;strong&gt;active development&lt;/strong&gt; where you're constantly tweaking code, it can feel a bit heavyweight. Jumping in and out of container builds or dealing with bind mounts just isn't as immediate as simply entering an interactive shell. That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: Comparing Perplexity vs. local tool reports
&lt;/h2&gt;

&lt;p&gt;So, I put a short summary request for &lt;em&gt;"LLM history, roots, most impactful studies (scan all starting with 20th century)"&lt;/em&gt; to both &lt;strong&gt;Perplexity&lt;/strong&gt; and the &lt;strong&gt;local tool&lt;/strong&gt; we've been discussing. My goal was to show that for basic deep research, you don't necessarily need to pay for a service; you can configure your own using free tiers like Gemini for LLM tasks and a free search API like Serper, which the local tool utilises to scrape the web.&lt;/p&gt;

&lt;p&gt;To get an unbiased comparison of the results, I asked a third LLM to analyze the style and substance of the two reports. Here's a summary of its findings:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While Article 2 (Perplexity) is more concise and its table of "Most Impactful Studies" is a valuable quick reference, Article 1 (Local tool) provides a richer, more detailed, and better-structured historical account that allows for a deeper understanding of the evolution of LLMs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  And you know what?:
&lt;/h3&gt;

&lt;p&gt;You can always tweak the local tool implementation per your own needs! (e.g. I've adjusted it specifically for my needs to narrow down the set of resources I want it to look into). I mean, it's programming, you do whatever you want! And using nix preparing your dev setup as simple as running just 1 command, &lt;code&gt;nix develop&lt;/code&gt;!&lt;/p&gt;

</description>
      <category>nix</category>
      <category>llm</category>
      <category>rag</category>
      <category>python</category>
    </item>
    <item>
      <title>Ruby on Rails: Your Service Layer is a Lie</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Wed, 15 Jan 2025 10:00:57 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/ruby-on-rails-your-service-layer-is-a-lie-1nj8</link>
      <guid>https://forem.com/alexander_shagov/ruby-on-rails-your-service-layer-is-a-lie-1nj8</guid>
      <description>&lt;p&gt;If you're a Rails developer, you've probably seen (or written) code that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="no"&gt;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# In your controller&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s1"&gt;'User was successfully created.'&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look familiar? We've all been there. We've all written these service objects because "that's what good Rails developers do." But let me ask you something: What value did that service layer actually add?&lt;/p&gt;

&lt;p&gt;We've been told that service layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separate business logic from models&lt;/li&gt;
&lt;li&gt;Make code more testable&lt;/li&gt;
&lt;li&gt;Keep controllers thin&lt;/li&gt;
&lt;li&gt;Follow single responsibility principle&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  When Your Service is Just Extra Typing
&lt;/h4&gt;

&lt;p&gt;Many Rails codebases are littered with service objects that do nothing but proxy method calls to ActiveRecord models. Let's look at a real-world example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommentService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;post: &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What did we gain here? Nothing except an extra file to maintain and an additional layer to jump through while debugging. The service is literally just passing through parameters to Comment.create. Even worse, we've actually lost functionality compared to working with the model directly - we no longer have access to ActiveRecord's rich API for handling validation errors and callbacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  When You Actually Need a Service Layer
&lt;/h3&gt;

&lt;p&gt;Let's be clear: Service objects aren't always bad. (In fact I've written a separate article on rethinking service objects: &lt;a href="https://dev.to/alexander_shagov/ruby-on-rails-rethinking-service-objects-4l0b"&gt;https://dev.to/alexander_shagov/ruby-on-rails-rethinking-service-objects-4l0b&lt;/a&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Orchestrating Complex Operations
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Orders&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProcessingWorkflow&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:start_processing&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:process_payment&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:allocate_inventory&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:create_shipping_label&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:send_confirmation&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:complete_processing&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_processing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'processing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:processing_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;payment_token&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;payment: &lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:payment_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allocate_inventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_shipping_label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_confirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;OrderMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:notification_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;complete_processing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'processed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:completion_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Handling External Services
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Subscriptions&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeWorkflow&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:validate_subscription&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:create_stripe_customer&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:create_stripe_subscription&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:update_user_records&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Subscriptions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ValidationContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:validation_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_stripe_customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ... stripe code&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_stripe_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ... stripe code&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_user_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;stripe_customer_id: &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:customer&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;stripe_subscription_id: &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:subscription&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;subscription_status: &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:subscription&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:record_update_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Complex Business Rules
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Loans&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationProcess&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:validate_application&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:check_credit_score&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:evaluate_debt_ratio&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:calculate_risk_score&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:determine_approval&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:process_result&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Loans&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ApplicationContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:validation_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_credit_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:application&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credit_score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;
        &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:credit_score_too_low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Credit score below minimum requirement"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_debt_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Loans&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DebtRatioCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:application&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.43&lt;/span&gt;
        &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:debt_ratio_too_high&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Debt-to-income ratio exceeds maximum"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;debt_ratio: &lt;/span&gt;&lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_risk_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;determine_approval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:application&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:approved&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;rate_calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Loans&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InterestRateCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;application: &lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;risk_score: &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:risk_score&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;interest_rate: &lt;/span&gt;&lt;span class="n"&gt;rate_calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;approval_date: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="no"&gt;LoanMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approval_notice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'rejected'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="no"&gt;LoanMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rejection_notice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:processing_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The examples above represent what I consider valid uses of a service layer - or more accurately, business processes. They demonstrate clear cases where abstraction adds real value: complex workflows, external service integration, and domain-rich business rules and etc.&lt;br&gt;
But the key takeaway isn't just about when to use these patterns - it's about questioning our default approaches to architecture. Too often, we reach for service objects because "that's how it's done" or because we've read that "fat models are bad." Instead, we should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start simple - directly in models and controllers&lt;/li&gt;
&lt;li&gt;Let complexity guide abstraction - not the other way around&lt;/li&gt;
&lt;li&gt;Think in terms of processes and workflows rather than generic "services"&lt;/li&gt;
&lt;li&gt;Question established patterns - just because everyone's doing it doesn't make it right for your specific case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;! More importantly, if you're part of a large team, establish and agree on a &lt;em&gt;unified&lt;/em&gt; approach first. Having half your codebase using traditional service objects and the other half using process-oriented workflows will likely create more problems than it solves. Architectural decisions should be team decisions, backed by clear conventions and documentation.&lt;br&gt;
Remember: Every layer of abstraction is a trade-off. Make sure you're getting enough value to justify the complexity cost.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>architecture</category>
      <category>dry</category>
    </item>
    <item>
      <title>Why Use Nix package manager, Even on macOS?</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Mon, 23 Sep 2024 07:55:20 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/why-use-nix-even-on-macos-786</link>
      <guid>https://forem.com/alexander_shagov/why-use-nix-even-on-macos-786</guid>
      <description>&lt;p&gt;Some time ago I wrote a short article on nix first steps (&lt;a href="https://dev.to/alexander_shagov/nix-first-steps-4066"&gt;https://dev.to/alexander_shagov/nix-first-steps-4066&lt;/a&gt;). However, I realized that I need one more to quickly explain why generally prefer nix over non-nix setups. So here it is.&lt;/p&gt;




&lt;p&gt;As a macOS user, you might wonder why you should consider using Nix (package manager) when you already have package managers like Homebrew.&lt;/p&gt;

&lt;p&gt;⭐️ One of the most significant advantages of Nix is its ability to create reproducible environments. This means you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easily share your exact development setup with teammates.&lt;/li&gt;
&lt;li&gt;Recreate your entire system setup on a new machine with minimal effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐️ With Nix (especially when using Home Manager), you define your entire system configuration in code. This approach offers several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your system setup becomes version-controlled.&lt;/li&gt;
&lt;li&gt;You can review and roll back changes easily.&lt;/li&gt;
&lt;li&gt;No more conflicts between different versions of the same library.&lt;/li&gt;
&lt;li&gt;Easy testing of different software versions without affecting your system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐️ Nix, especially with flakes, allows you to define project-specific environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each project can have its own set of tools and dependencies.&lt;/li&gt;
&lt;li&gt;No need to globally install project-specific tools, reducing system clutter.&lt;/li&gt;
&lt;li&gt;If a local dependency update breaks something, you can easily roll back to a previous state (e.g. roll back node.js to the previous version in 1 click).&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;As a macOS user, I initially hesitated to adopt Nix, comfortable with my Homebrew + Ruby version manager + Node version manager + Docker setup. &lt;br&gt;
However, after experiencing the reproducibility and declarative nature of Nix, I've found it invaluable. It's particularly useful for managing multiple development environments and ensuring consistency across my personal and work machines.&lt;/p&gt;

&lt;p&gt;While the learning curve can be steep, the long-term benefits in terms of system management and development workflow have been worth the initial investment.&lt;/p&gt;

&lt;p&gt;Diving into the Nix world might be overwhelming at first. Check out the 'First Steps' article (&lt;a href="https://dev.to/alexander_shagov/nix-first-steps-4066"&gt;https://dev.to/alexander_shagov/nix-first-steps-4066&lt;/a&gt;), which helps define the important vocabulary.&lt;/p&gt;

</description>
      <category>nix</category>
      <category>devex</category>
      <category>discuss</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Balancing Authenticity and Self-Promotion in the Tech Industry</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Mon, 23 Sep 2024 07:40:40 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/balancing-authenticity-and-self-promotion-in-the-tech-industry-3jl3</link>
      <guid>https://forem.com/alexander_shagov/balancing-authenticity-and-self-promotion-in-the-tech-industry-3jl3</guid>
      <description>&lt;p&gt;Picture this: You're a software developer at MegaCorp, writing code day in and day out. Your contributions, while necessary, often feel like drops in a vast ocean. Then comes the time to update your resume or LinkedIn profile. Suddenly, you're faced with a choice: do you stick to the unpolished truth, or do you make it sound big?&lt;/p&gt;

&lt;h3&gt;
  
  
  Welcome to the modern developer's dilemma.
&lt;/h3&gt;

&lt;p&gt;In today's hyper-competitive GPT-powered tech industry, the pressure to stand out is immense. We're told to "build our personal brand," "showcase our achievements," and "sell ourselves" to potential employers. But what happens when the reality of our day-to-day work doesn't quite match up to these expectations?&lt;/p&gt;

&lt;p&gt;Let's be honest - not every developer is working on revolutionary AI algorithms or building the next unicorn startup. Many of us are maintaining legacy systems, squashing bugs, or implementing small features that, while important, hardly set the world on fire. Yet, scroll through LinkedIn, and you'd think every developer was single-handedly revolutionizing their industry.&lt;/p&gt;

&lt;p&gt;This gap between reality and marketing creates tension. On one side, there's the pull towards honesty and being real. On the other, there's the practical need to compete in a job market that seems to reward those who can write the most impressive-sounding bullet points.&lt;br&gt;
And... it often works. HR teams and recruiters, often not knowing the technical details of our work, can be swayed by well-written stories of impact and achievement. A basic task reframed as a "critical initiative" might just be the difference between getting an interview or being lost in the resume pile.&lt;/p&gt;

&lt;p&gt;And the situation is getting worse with GPT-powered systems checking GPT-generated CVs..&lt;/p&gt;

&lt;p&gt;But at what cost?&lt;/p&gt;




&lt;p&gt;Original: "Maintained backend systems."&lt;br&gt;
Polished: "Architected and implemented mission-critical backend infrastructure, resulting in 99.99% uptime and 30% improvement in system efficiency."&lt;/p&gt;




&lt;p&gt;Comment below with your experiences navigating authenticity in the tech industry. How do you balance self-promotion with genuine representation of your work?&lt;/p&gt;

</description>
      <category>interview</category>
      <category>career</category>
      <category>discuss</category>
      <category>hr</category>
    </item>
    <item>
      <title>The "IO to the Boundary" principle</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Tue, 10 Sep 2024 14:59:13 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/many-architectural-principles-can-be-simplified-to-the-io-to-the-boundary-principle-2hg6</link>
      <guid>https://forem.com/alexander_shagov/many-architectural-principles-can-be-simplified-to-the-io-to-the-boundary-principle-2hg6</guid>
      <description>&lt;p&gt;In software architecture, many principles and patterns have emerged over time. However, a lot of these can be boiled down to a single idea: the "IO to the Boundary" principle. This principle suggests that all input/output operations, like database queries, API calls, or file system interactions, should be pushed to the edges (or boundaries) of your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's look at what some other ideas tell us:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Functional Core, Imperative Shell: This principle suggests keeping the core of your application pure and functional, while handling side effects (IO) in an outer layer. This is basically another way of saying "IO to the Boundary."&lt;/li&gt;
&lt;li&gt;Clean Architecture: Proposed by Robert C. Martin, this architecture emphasizes separating concerns and having dependencies point inwards, which fits with keeping IO at the edges.&lt;/li&gt;
&lt;li&gt;Command Query Responsibility Segregation (CQRS): While not mainly about IO boundaries, CQRS often leads to separating read and write operations. This separation can support the "IO to the Boundary" principle by making it easier to isolate and manage IO operations at the system's edges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Client Code interacts with both WeatherAPI (to create an object instance) and WeatherReport (to generate a report).&lt;/li&gt;
&lt;li&gt;WeatherReport uses WeatherAPI internally to fetch temperature data.&lt;/li&gt;
&lt;li&gt;WeatherAPI encapsulates the IO operations, making the actual HTTP request to the External Weather API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This structure keeps the core application logic (WeatherReport) free from direct IO concerns, pushing those responsibilities to the edges (WeatherAPI and beyond).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client Code       WeatherReport       WeatherAPI         External Weather API
     |                   |                   |                  |
     |                   |                   |                  |
     |  new WeatherAPI()                     |                  |
     |---------------------------------------&amp;gt;                  |
     |                   |                   |                  |
     |  new WeatherReport(weather_api)       |                  |
     |------------------&amp;gt;|                   |                  |
     |                   |                   |                  |
     |  generate_report('New York')          |                  |
     |------------------&amp;gt;|                   |                  |
     |                   |                   |                  |
     |                   | get_temperature('New York')          |
     |                   |------------------&amp;gt;|                  |
     |                   |                   |                  |
     |                   |                   | HTTP GET request |
     |                   |                   |-----------------&amp;gt;|
     |                   |                   |                  |
     |                   |                   | JSON response    |
     |                   |                   |&amp;lt;-----------------|
     |                   |                   |                  |
     |                   |     temperature   |                  |
     |                   |&amp;lt;------------------|                  |
     |                   |                   |                  |
     |  formatted report |                   |                  |
     |&amp;lt;------------------|                   |                  |
     |                   |                   |                  |

    [Application Boundary]              [IO Boundary]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Note on oversimplification
&lt;/h3&gt;

&lt;p&gt;From my perspective, the key to effective architecture is finding the right balance between simplification and necessary complexity (sounds vague but that's true..). It's becoming clear that most mid-sized projects don't need the level of complexity they often include. But some do. While I recognize that the real world is more complex, with evolving projects, changing teams, and loss of knowledge when an organization changes, it's crucial to remember that sometimes the best abstraction is no abstraction at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code samples
&lt;/h2&gt;

&lt;p&gt;Let's consider "good" and "bad" examples (I'll be using Ruby and Ruby on Rails to convey the ideas)&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/weather_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;
    &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'WEATHER_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://api.weather.com/v1/temperature?city=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;key=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'temperature'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;feels_like&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'feels_like'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Weather Report for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"Temperature: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°C&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"Feels like: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;feels_like&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°C&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
      &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"It's hot outside! Stay hydrated."&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
      &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"It's cold! Bundle up."&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"The weather is mild. Enjoy your day!"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;plain: &lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's evident that the code smells:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mixing IO operations (API call) with business logic in the controller.&lt;/li&gt;
&lt;li&gt;Directly parsing and manipulating data in the controller.&lt;/li&gt;
&lt;li&gt;Generating the report text within the controller action.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Good example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/weather_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WeatherReport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;city: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;plain: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value!&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;plain: &lt;/span&gt;&lt;span class="s2"&gt;"Error: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/business_processess/weather_report/generate.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dry/transaction'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;WeatherReport&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Generate&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:validate_input&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:fetch_weather_data&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:generate_report&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Params&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;city: &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;city: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_weather_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WeatherGateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;city: &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;weather: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WeatherReport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;compose&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/business_processess/weather_report/weather_gateway.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherGateway&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:result&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://api.weather.com/v1/temperature?city=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;key=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'WEATHER_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;temperature: &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'temperature'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;feels_like: &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'feels_like'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Failed to fetch weather data: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/business_processess/weather_report/weather_report.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherReport&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;
    &lt;span class="vi"&gt;@weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What has changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separating concerns: The controller only handles HTTP-related tasks.&lt;/li&gt;
&lt;li&gt;Using dry-transaction to create a clear flow of operations, with each step having a single responsibility.&lt;/li&gt;
&lt;li&gt;Pushing IO operations (API calls) to the &lt;strong&gt;boundary&lt;/strong&gt; in the WeatherAPI service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Note on complexity
&lt;/h3&gt;

&lt;p&gt;We should be realistic though, if we're talking about a simple 1-pager app generating the weather reports, well, who cares? It just works and we definitely do not want to introduce any additional layers. &lt;br&gt;
However, if we care about the future extendability, thinking about these kind of things is crucial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final note
&lt;/h3&gt;

&lt;p&gt;Many of the architectural principles and patterns we encounter today are not entirely new concepts, but rather evolved or repackaged ideas from the past. I hope that you see now that the "IO to the Boundary" principle is one such idea that has been expressed in various forms over the years.&lt;/p&gt;

&lt;p&gt;Comments / suggestions appreciated! 🙏&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>rails</category>
      <category>designpatterns</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Ruby on Rails: Rethinking Service Objects</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Wed, 04 Sep 2024 13:03:36 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/ruby-on-rails-rethinking-service-objects-4l0b</link>
      <guid>https://forem.com/alexander_shagov/ruby-on-rails-rethinking-service-objects-4l0b</guid>
      <description>&lt;p&gt;In the Ruby on Rails community, service objects have gained popularity as a way to encapsulate business logic and keep controllers and models lean. However, there's a growing trend of misusing service objects, particularly those with "-er" or "-or" suffixes. This article aims to shed light on poorly implemented service objects and provide guidance on creating more effective, domain-focused alternatives.&lt;/p&gt;




&lt;p&gt;Many developers tend to name their service objects with suffixes like &lt;code&gt;-er&lt;/code&gt; or &lt;code&gt;-or&lt;/code&gt;, such as &lt;code&gt;ArticleCreator&lt;/code&gt; or &lt;code&gt;UserAuthenticator&lt;/code&gt;. While this naming convention may seem intuitive, it often leads to a procedural style of programming rather than embracing the object-oriented nature of Ruby.&lt;/p&gt;

&lt;p&gt;It's becoming evident that service objects the way we often see them are not actually "objects", but just a procedures.. or better to say, &lt;strong&gt;processess&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's take a look at the real-world example of a codebase powering the dev.to community: &lt;a href="https://github.com/forem/forem/blob/main/app/services/articles/creator.rb" rel="noopener noreferrer"&gt;https://github.com/forem/forem/blob/main/app/services/articles/creator.rb&lt;/a&gt;.&lt;br&gt;
The class seems okay at first glance; at least the code is organized and lives in its own space. It could be workable. However, we're just hiding the problem in the long term.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The code structure is taken from https://github.com/forem/forem/blob/main/app/services/articles/creator.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleCreator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="vi"&gt;@article_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article_params&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;rate_limit!&lt;/span&gt;
    &lt;span class="n"&gt;create_article&lt;/span&gt;
    &lt;span class="n"&gt;subscribe_author&lt;/span&gt;
    &lt;span class="n"&gt;refresh_auto_audience_segments&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;published?&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rate_limit!&lt;/span&gt;
    &lt;span class="c1"&gt;# Rate limiting logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_article&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@article_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribe_author&lt;/span&gt;
    &lt;span class="no"&gt;NotificationSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notifiable: &lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;config: &lt;/span&gt;&lt;span class="s2"&gt;"all_comments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_auto_audience_segments&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh_auto_audience_segments&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Domain-Centric Approach
&lt;/h2&gt;

&lt;p&gt;Instead of focusing solely on actions (creating, updating, deleting), we should model our service objects around domain concepts and processes. Hence my suggestion: let's stop pretending service objects are objects and start calling them &lt;strong&gt;processess&lt;/strong&gt; instead. &lt;br&gt;
NOTE: I'm not talking about &lt;em&gt;DDD&lt;/em&gt; (Domain-Driven design) since it's a whole separate topic, however, we can be inspired by some of the concepts.&lt;/p&gt;

&lt;p&gt;I'll be using &lt;code&gt;dry-*&lt;/code&gt; libraries to demonstrate how a process might have looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Articles&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PublishingProcess&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:validate&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:rate_limit&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:create_article&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:subscribe_author&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:refresh_auto_audience_segments&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:notify_subscribers&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Articles&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ValidationContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rate_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Articles&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PublishingQuota&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;within_limit?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:rate_limited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Articles&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:params&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;article: &lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribe_author&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Articles&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AuthorSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:article&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_auto_audience_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# NOTE: the naming might not be the best one for this case since I'm not aware about the actual business logic behind&lt;/span&gt;
      &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Articles&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Audience&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh_segments&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_subscribers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;publication&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Articles&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SubscriberNotification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:article&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;publication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See, not only the business process is very well defined, but it also deals with domain-centric objects: &lt;code&gt;Articles::ValidationContract&lt;/code&gt;, &lt;code&gt;Articles::PublishingQuota&lt;/code&gt;, &lt;code&gt;Articles::Repository&lt;/code&gt;, &lt;code&gt;Articles::AuthorSubscription&lt;/code&gt; and etc.&lt;/p&gt;




&lt;p&gt;⭐ Thanks to having a clear separation between &lt;strong&gt;Processes&lt;/strong&gt; and &lt;strong&gt;Domain objects&lt;/strong&gt; we'll stop pretending that we play in OOP game when dealing with service objects.&lt;/p&gt;

&lt;p&gt;Let's imagine an ideal world where each app consisted of Process-like implementations. As a result, our app might look like a set of business processes. One process might start another as an async job, or some processes might be executed in the background on scheduled basis. Yet, it doesn't change the fact that an app consists of a set of business processes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Hotel Booking Process&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Bookings&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HotelReservationProcess&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:check_room_availability&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:calculate_total_cost&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:process_deposit&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:create_reservation&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:send_confirmation&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:schedule_reminders&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# User Registration Process&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Users&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegistrationProcess&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:validate_user_data&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:check_unique_email&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:create_user&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:send_verification_email&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:assign_default_roles&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;I think it goes without saying that having a clear separation of processes and a set of domain-centric objects powering these processes is a cleaner way to write business logic. However, I understand that the "ideal" world and the "real" world are sometimes completely different pictures. It always boils down to engineering contracts established in a particular team or the whole organization, and sometimes it's not that easy to change habits, especially when we're talking about legacy production applications.&lt;/p&gt;

&lt;p&gt;Nevertheless, what we can do is refine our mental models to try to perceive "business processes" instead of "some" objects, and incorporate the business language into the domain language of our system.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Nix first steps</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Wed, 04 Sep 2024 08:33:10 +0000</pubDate>
      <link>https://forem.com/alexander_shagov/nix-first-steps-4066</link>
      <guid>https://forem.com/alexander_shagov/nix-first-steps-4066</guid>
      <description>&lt;p&gt;It might be overwhelming to dive into the Nix world, but this little glossary should help refine your understanding of the tools you might encounter when starting your journey.&lt;/p&gt;

&lt;p&gt;UPD: I wrote one more short article to articulate why I prefer nix over non-nix setups in general: &lt;a href="https://dev.to/alexander_shagov/why-use-nix-even-on-macos-786"&gt;https://dev.to/alexander_shagov/why-use-nix-even-on-macos-786&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Nix Package Manager&lt;/strong&gt; – The core tool that allows declarative and reproducible package management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NixOS&lt;/strong&gt; – A Linux distribution built entirely on the Nix package manager. It's better to start with nix package manager alone before switching to NixOS completely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Home Manager&lt;/strong&gt; – Manages user packages, dotfiles, environment variables, shell configurations, and more. Allows for portable user environments across different machines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flakes&lt;/strong&gt; – Provides a standardized structure for Nix projects, improving reproducibility and composability. It's worth noting that while Flakes are still technically experimental, they have been widely adopted by the Nix community due to their benefits. Many newer Nix configurations and projects are built using Flakes.&lt;/p&gt;




&lt;h3&gt;
  
  
  Old flow vs New flow
&lt;/h3&gt;

&lt;p&gt;When you start searching for actual commands, you might encounter that some of them have different syntax (e.g., 'nix build' vs 'nix-build').&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes" rel="noopener noreferrer"&gt;https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Based on the article, there are two "flows" or approaches to using Nix: the old (or classic) flow and the new flow using flakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New Flow (Flakes and New CLI):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Introduces flake.nix and flake.lock files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uses new CLI commands that start with "nix" (e.g., nix develop, nix shell, nix build).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replaces channels with explicit inputs in flake.nix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Aims for improved reproducibility and composability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Old Flow (Classic Nix):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Used commands like nix-channel, nix-env, nix-shell, and nix-build.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Relied on channels for package management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Less standardized structure for Nix projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Could lead to less reproducible builds in some cases.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new flow is essentially an evolution of Nix, addressing some limitations of the classic approach and borrowing ideas from other modern package managers (like npm, Cargo, etc.).&lt;/p&gt;

&lt;p&gt;Assuming you're not using NixOS itself but, let's say, macOS, your flow of working with your development environment might look like this:&lt;/p&gt;

&lt;p&gt;1) Use &lt;strong&gt;home-manager&lt;/strong&gt; (preferably with flakes) to manage user-specific packages, tools, and configurations. This includes system-wide tools like Emacs, as well as dotfiles and environment settings.&lt;/p&gt;

&lt;p&gt;2) For each project you're working on, create a &lt;code&gt;flake.nix&lt;/code&gt; file to define the project-specific development environment and dependencies.&lt;/p&gt;




&lt;p&gt;The aim of the article was to provide a good starting point and set up the mental model for further readings. Even though I didn't set a goal to share specific code snippets, here's the example of a home-manager (&lt;em&gt;home.nix&lt;/em&gt;) file defining the global configuration on my MacOS. This configuration defines the global state of my system. Any project-related pieces live a in separate &lt;em&gt;flake.nix&lt;/em&gt; files in their own namespaces. NOTE: I'm not using &lt;strong&gt;brew&lt;/strong&gt; anyhow!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;username&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;homeDirectory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;homeDirectory&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stateVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"24.05"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;direnv&lt;/span&gt;
    &lt;span class="nv"&gt;ripgrep&lt;/span&gt;
    &lt;span class="nv"&gt;htop&lt;/span&gt;
    &lt;span class="nv"&gt;inetutils&lt;/span&gt;
    &lt;span class="nv"&gt;jq&lt;/span&gt;
    &lt;span class="nv"&gt;emacs29&lt;/span&gt;
    &lt;span class="nv"&gt;emacsPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;vterm&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;shellAliases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# ============ git aliases =====================&lt;/span&gt;
    &lt;span class="nv"&gt;gs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git status"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;gcm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git checkout main &amp;amp;&amp;amp; git pull"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;gc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git checkout"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;sessionVariables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;EDITOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"emacs"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c"&gt;# ======== Zsh configuration ============&lt;/span&gt;

  &lt;span class="nv"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;zsh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# Enable managing the Zsh configuration&lt;/span&gt;

    &lt;span class="nv"&gt;initExtra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;      # Private ENV vars configuration&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      if [ -f ~/.bashrc_private ]; then&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        . ~/.bashrc_private&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      fi    &lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;      # ========= Prompt with a branch ========&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      function git_branch_name() {&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;           git rev-parse --abbrev-ref HEAD 2&amp;gt;/dev/null || echo '='&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      }&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      autoload -Uz colors &amp;amp;&amp;amp; colors&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      setopt prompt_subst&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      prompt='%F{yellow}%~/%f [%F{green}$(git_branch_name)%f] &amp;gt; '&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;direnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c"&gt;# enableBashIntegration = true; # see note on other shells below&lt;/span&gt;
    &lt;span class="nv"&gt;nix-direnv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c"&gt;# Let Home Manager install and manage itself.&lt;/span&gt;
  &lt;span class="nv"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;home-manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any time I need to add/remove system-wide package, or adjust, let's say, &lt;code&gt;zsh&lt;/code&gt; configuration, it just boils down to editing the &lt;em&gt;home.nix&lt;/em&gt; file and running the &lt;code&gt;home-manager switch&lt;/code&gt; to apply the change (or &lt;code&gt;home-manager build&lt;/code&gt; to see if a build compiles correctly).&lt;/p&gt;




&lt;h3&gt;
  
  
  Where to look for packages
&lt;/h3&gt;

&lt;p&gt;Given you want to install &lt;code&gt;htop&lt;/code&gt;. Just use the &lt;a href="https://search.nixos.org/packages?channel=24.05&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=htop" rel="noopener noreferrer"&gt;search.nixos.org&lt;/a&gt; and put the package name into your &lt;em&gt;home.nix&lt;/em&gt; file. Run &lt;code&gt;home-manager switch&lt;/code&gt; and you'll have your &lt;code&gt;htop&lt;/code&gt; configured automatically (e.g. &lt;code&gt;/Users/&amp;lt;username&amp;gt;/.nix-profile/bin/htop&lt;/code&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  How to install a specific version of a package?
&lt;/h4&gt;

&lt;p&gt;If you want to install a specific version of a package (not the latest one by default), you can search at &lt;a href="https://www.nixhub.io/packages/htop" rel="noopener noreferrer"&gt;nixhub.io&lt;/a&gt;. There, you'll see a specific &lt;strong&gt;Nixpkgs Reference&lt;/strong&gt; you should use in your &lt;em&gt;home.nix&lt;/em&gt; file to point to a specific version you're interested in.&lt;/p&gt;




&lt;h3&gt;
  
  
  Useful links for further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Introduction to Flakes: &lt;a href="https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes" rel="noopener noreferrer"&gt;https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Nix flakes syntax basics: &lt;a href="https://serokell.io/blog/practical-nix-flakes" rel="noopener noreferrer"&gt;https://serokell.io/blog/practical-nix-flakes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official nix package manager step-by-step guide: &lt;a href="https://nixos.org/guides/nix-pills/01-why-you-should-give-it-a-try" rel="noopener noreferrer"&gt;https://nixos.org/guides/nix-pills/01-why-you-should-give-it-a-try&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A good article where a person shows how they cleaned up their $HOME with nix: &lt;a href="https://juliu.is/tidying-your-home-with-nix/" rel="noopener noreferrer"&gt;https://juliu.is/tidying-your-home-with-nix/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;⭐ Comments and more useful articles for nix beginners appreciated!&lt;/p&gt;

</description>
      <category>nix</category>
      <category>nixos</category>
      <category>devex</category>
    </item>
  </channel>
</rss>
