<?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>DEV Community: Alexander Shagov</title>
    <description>The latest articles on DEV Community by Alexander Shagov (@alexander_shagov).</description>
    <link>https://dev.to/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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1997001%2F63bf5fec-70b7-4b7b-9080-e1c41326c365.jpeg</url>
      <title>DEV Community: Alexander Shagov</title>
      <link>https://dev.to/alexander_shagov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexander_shagov"/>
    <language>en</language>
    <item>
      <title>How Jev decision models work (and how to build your own on MLX)</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:24:48 +0000</pubDate>
      <link>https://dev.to/alexander_shagov/how-jev-decision-models-work-and-how-to-build-your-own-on-mlx-2lph</link>
      <guid>https://dev.to/alexander_shagov/how-jev-decision-models-work-and-how-to-build-your-own-on-mlx-2lph</guid>
      <description>&lt;p&gt;The web is bloated with Jev posts. The thing is a few weeks old, and my feeds are already full of it.. benchmarks, hot takes, threads about decision models being the future of agents. Jev, if you've managed to avoid the hype, is &lt;a href="https://typesafe.ai" rel="noopener noreferrer"&gt;TypeSafe AI&lt;/a&gt;'s hosted decision API where you send a situation and a few typed questions, it returns the answer with a probability for every option.&lt;/p&gt;

&lt;p&gt;Two spoilers, because I like roads with signposts. &lt;strong&gt;Classical ML never left the chat&lt;/strong&gt;, and the newest tool in the box still doesn't answer the oldest question, whether you actually need it in the first place.&lt;/p&gt;




&lt;p&gt;At some point I stopped skimming news and got really curious about the machinery underneath. How does a model &lt;em&gt;decide&lt;/em&gt; instead of write? What is actually different in there?&lt;/p&gt;

&lt;p&gt;The short answer turned into a project, and the project turned into a story about building a thing and then discovering, layer by layer (sorry), that most of it already existed. It's called &lt;strong&gt;&lt;a href="https://github.com/alexshagov/take-my-layer" rel="noopener noreferrer"&gt;take-my-layer&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Init
&lt;/h2&gt;

&lt;p&gt;I'll be honest, I never thought I'd invented something. I knew reading probabilities off logits was a thing and the Jev posts themselves hint at it. But knowing a trick exists and holding it in your own hands are different things, and what I didn't know was how far it stretches. I took a 4-bit Qwen model on a laptop, no training, no labels, questions made up on the spot. That part was worth testing.&lt;/p&gt;

&lt;p&gt;So I went scouting before building anything serious. Kind of following my own advice from a post about &lt;a href="https://shahau.com/blog/fog-of-war/" rel="noopener noreferrer"&gt;fog of war&lt;/a&gt; as a mental model. The fog doesn't mean the territory is missing, &lt;strong&gt;it just means you haven't been there yet.&lt;/strong&gt; Reading papers is scouting. Building a small proof of concept is scouting. I expected to find maybe one or two adjacent things.&lt;/p&gt;

&lt;p&gt;Well.. the territory I found was huuuge :D&lt;/p&gt;




&lt;h2&gt;
  
  
  The trick is from 2021
&lt;/h2&gt;

&lt;p&gt;First stop. Reading answer probabilities instead of generating answer text. This is how the whole evaluation world has scored decoder-only models for years.. it's the &lt;code&gt;loglikelihood&lt;/code&gt; default in &lt;a href="https://github.com/EleutherAI/lm-evaluation-harness" rel="noopener noreferrer"&gt;lm-evaluation-harness&lt;/a&gt;. The prompting version has names (verbalizers, pattern-verbalizer pairs, cloze-style prompting). &lt;a href="https://arxiv.org/abs/2001.07676" rel="noopener noreferrer"&gt;PET&lt;/a&gt; (Schick &amp;amp; Schütze, EACL 2021) and &lt;a href="https://arxiv.org/abs/2009.07118" rel="noopener noreferrer"&gt;"Small Language Models Are Also Few-Shot Learners"&lt;/a&gt; (NAACL 2021) are the classics, &lt;a href="https://arxiv.org/abs/2012.15723" rel="noopener noreferrer"&gt;LM-BFF&lt;/a&gt; (ACL 2021) even automates the label-word search.&lt;/p&gt;

&lt;p&gt;What I'd been calling "reading the logits" has a standard name, a literature, a &lt;a href="https://github.com/thunlp/OpenPrompt" rel="noopener noreferrer"&gt;codified abstraction&lt;/a&gt;, and several thousand citations. The trick predates ChatGPT (obvious for people who are not the first day on the ML street).&lt;/p&gt;




&lt;h2&gt;
  
  
  The bugs are from 2021 too
&lt;/h2&gt;

&lt;p&gt;The naive version has a bug called position bias. Ask "A, B, C or D?" and the model leans toward whichever option is listed first, regardless of the options. I found that myself the fun way when the raw read on my coding data predicted &lt;code&gt;bash&lt;/code&gt; for 74 of 120 states. A bash magnet kind of. Its 0.57 accuracy was roughly bash's base rate in the data.&lt;/p&gt;

&lt;p&gt;Essentially every bug I hit someone had already hit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2102.09690" rel="noopener noreferrer"&gt;Calibrate Before Use&lt;/a&gt; (Zhao et al., ICML 2021): content-free priors to correct the model's answer skew.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2308.11483" rel="noopener noreferrer"&gt;Order sensitivity of option lists&lt;/a&gt; (Pezeshkpour &amp;amp; Hruschka, 2023).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2104.08786" rel="noopener noreferrer"&gt;Fantastically Ordered Prompts&lt;/a&gt; (Lu et al., ACL 2022): same disease, few-shot demonstrations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2309.03882" rel="noopener noreferrer"&gt;PriDe&lt;/a&gt; (ICLR 2024): the cheap permutation-plus-prior combination, which is more or less the cheaper cousin of my scheme.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2309.17249" rel="noopener noreferrer"&gt;Batch Calibration&lt;/a&gt; (ICLR 2024): the unified view of all of the above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My own fix (arrived at independently and with more cursing), was to list the options in several rotated orders, ask once per order, average. That's called permutation marginalization, and it is worth &lt;strong&gt;+4.7 points&lt;/strong&gt; of agreement with the reference over the naive single-prompt read (5 answers flip toward the reference, 0 away). All of that was known. Standing on shoulders of giants, etc.&lt;/p&gt;




&lt;h2&gt;
  
  
  Leaving early
&lt;/h2&gt;

&lt;p&gt;The next piece was speed. A full forward pass over a 4B model for a one-word answer is wasteful, and it turns out the decision signal sits pretty early. (Qwen3.5-4B has 32 blocks; layer selection kept picking 16 and 19, before the last full-attention blocks. The interesting states live in the linear-attention trunk.)&lt;/p&gt;

&lt;p&gt;The literature here is &lt;a href="https://arxiv.org/abs/2207.07061" rel="noopener noreferrer"&gt;CALM&lt;/a&gt; (NeurIPS 2022), early exit per token by confidence. In my case it's a &lt;em&gt;fixed&lt;/em&gt; half-depth exit for one fitted head, the layer-pruning family. Fine again, keep standing on the shoulders!&lt;/p&gt;

&lt;p&gt;In MLX land there is exactly one static precedent that I could find: &lt;a href="https://huggingface.co/dowands/NeoJev-Qwen3.8-27B-L56-MLX" rel="noopener noreferrer"&gt;NeoJev&lt;/a&gt;, a 27B 4-bit (~15 GB) that keeps 56 of 64 layers with a self-distilled head, 0.727 mean accuracy at 310 ms p50 on an M5 Pro. One detail I genuinely enjoyed is that a plain logit lens at their layer 56 reads 0.663. Same head-over-raw gap I measured on my own side, so kind of proving I'm moving in the right direction.&lt;/p&gt;

&lt;p&gt;A practical note for anyone following this route: &lt;code&gt;mlx-lm&lt;/code&gt; exposes no intermediate-layer hooks (issue #1053, still open as of date of this blog post), so a hand-rolled block loop is the only way in. Mine matches the stock forward to a max logit difference of about 0.037.&lt;/p&gt;




&lt;h2&gt;
  
  
  A warning from the probing people
&lt;/h2&gt;

&lt;p&gt;Fitting a tiny linear head on hidden states is also a literature: called probing. &lt;a href="https://arxiv.org/abs/1909.03368" rel="noopener noreferrer"&gt;Hewitt &amp;amp; Liang&lt;/a&gt; (2019) and &lt;a href="https://arxiv.org/abs/2102.12452" rel="noopener noreferrer"&gt;Belinkov's survey&lt;/a&gt; (2022) are the standard references, and their criticism is aimed at &lt;em&gt;claims&lt;/em&gt;. A probe is weak evidence about what a model "understands". I took that as a warning about wording but not about architecture. A production head is judged by held-out task accuracy on the served artifact (ours reads 0.826 on its holdout), not by what it supposedly reveals. Linear (as the probing people say) is the defensible capacity choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  And finally products
&lt;/h2&gt;

&lt;p&gt;The method hunt was humbling, in that almost everything was already there before. The product hunt was even more humbling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://typesafe.ai" rel="noopener noreferrer"&gt;Jev&lt;/a&gt; you've already met. &lt;a href="https://blog.cloudflare.com/clef-decision-models/" rel="noopener noreferrer"&gt;Cloudflare's Clef&lt;/a&gt; ships fine-tuned decision models with open Apache-2.0 weights (209 ms median, 38.8 ms for the flash variant). &lt;a href="https://unsloth.ai/docs/basics/train-your-own-decision-model-with-unsloth" rel="noopener noreferrer"&gt;Unsloth&lt;/a&gt; has a train-your-own recipe: LoRA plus a Clef-design head, their 0.8B reads 36% on typed-decisions before training and 73% after. Around those there's a whole cluster: Mini-Jev, mlx-decision, laya-mlx. Even the routing people are adjacent, though &lt;a href="https://arxiv.org/abs/2406.18665" rel="noopener noreferrer"&gt;RouteLLM&lt;/a&gt; and friends put a &lt;em&gt;separate&lt;/em&gt; model in front of the LLM, whereas I wanted the served model's &lt;strong&gt;own&lt;/strong&gt; distribution to be the classifier.&lt;/p&gt;

&lt;p&gt;(!) And then the one that mattered to me: &lt;a href="https://github.com/nokia-applied-research/AnyJev" rel="noopener noreferrer"&gt;AnyJev&lt;/a&gt;, Nokia Applied Research's open method and library (&lt;a href="https://arxiv.org/abs/2610.00831" rel="noopener noreferrer"&gt;technical report&lt;/a&gt;). Essentially I've taken &lt;strong&gt;AnyJev's method, deployed on the Mac&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's actually mine
&lt;/h2&gt;

&lt;p&gt;An honest ledger, because I care about this part. The repo's docs have a section literally titled "honest lineage", and it says explicitly that I did not invent the trick. What's original though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The missing MLX backend for AnyJev's toolbox (an original implementation of their documented backend contract; they never shipped one).&lt;/li&gt;
&lt;li&gt;The small-memory end: a 4-bit 4B, about 3 GB resident, versus NeoJev's 15.&lt;/li&gt;
&lt;li&gt;Zero-training deployment: the frozen model works the moment it downloads. No dataset, no training run, no labels.&lt;/li&gt;
&lt;li&gt;The observe() loop: while a shadow proxy watches real agent traffic, it quietly banks "what I predicted" next to "what actually happened", and one command refits a head from that bank. As far as I could find, nobody ships fit-on-your-traffic heads.&lt;/li&gt;
&lt;li&gt;And the measurements of course.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is a new mechanism. It's just glue and a scorecard. BTW! I wrote about &lt;a href="https://shahau.com/blog/taste/" rel="noopener noreferrer"&gt;taste&lt;/a&gt; a while ago and there (TLDR) I say that when production becomes abundant, judgment becomes scarce. The judgment here was mostly deciding what &lt;em&gt;not&lt;/em&gt; to claim.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;The repo holds itself to wording rules that I want to keep in this post too: fidelity claims say "aligned with the reference", never "more correct"; accuracy claims name the arena and the model size. So, with that:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;what was measured&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agreement with the real Jev API, 300 non-coding questions&lt;/td&gt;
&lt;td&gt;4B &lt;strong&gt;75.7%&lt;/strong&gt;, 9B &lt;strong&gt;82.0%&lt;/strong&gt;; Jev agrees with itself 98.7%, which is the ceiling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readout vs greedy decoding of the same model, same 300 questions&lt;/td&gt;
&lt;td&gt;+4.7 (4B), +6.3 (9B), McNemar p = 0.003; on long inputs greedy Choice answers collapse (46% vs 85% agreement)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://huggingface.co/datasets/mteb/banking77" rel="noopener noreferrer"&gt;BANKING77&lt;/a&gt;, 10 intents, gold labels&lt;/td&gt;
&lt;td&gt;87.5% (4B), 91.25% (9B), Jev 95.5%: a trained model wins on its home turf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://huggingface.co/datasets/LocalLLaMA/typed-decisions" rel="noopener noreferrer"&gt;typed-decisions&lt;/a&gt;, zero training&lt;/td&gt;
&lt;td&gt;61.6% (4B), 60.4% (9B), vs 73-78% for models fine-tuned on it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency, long input, 5 questions in one request&lt;/td&gt;
&lt;td&gt;22.2 s down to &lt;strong&gt;3.7 s&lt;/strong&gt; mean (the state is read once); the coding head answers in ~0.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The losses are the point, in a way. A trained model beats a zero-training readout whenever labels and a fixed label set exist. &lt;strong&gt;Train a classifier!&lt;/strong&gt; This is for questions and options you cannot enumerate ahead of time, on a machine where the data cannot leave.&lt;/p&gt;

&lt;p&gt;Here's one real exchange on my M5:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8095/v1/systemone &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"state": "Ticket #4821 from Dana (Pro plan, customer for 14 months): \"Since this morning every export to CSV fails with a 502. We run payroll from those exports at 3pm today.\" Status page: all green.",
 "questions": {
   "route": {"type": "Choice", "instructions": "Which team should take this first?", "options": ["billing", "platform-oncall", "account-management", "docs"]},
   "escalate_now": {"type": "Noul", "instructions": "Does this need the on-call engineer right now?"},
   "urgency": {"type": "Score", "instructions": "How urgent is this ticket?", "levels": ["low", "medium", "high", "critical"]}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-&amp;gt; {"model": "mlx-community/Qwen3.5-4B-MLX-4bit",
    "answers": {
      "route": {"answer": "platform-oncall", "confidence": 0.8516, "probabilities": {"billing": 0.0744, "platform-oncall": 0.8516, "account-management": 0.0579, "docs": 0.0161}},
      "escalate_now": {"answer": true, "confidence": 0.637, "probabilities": {"Yes": 0.637, "No": 0.363}},
      "urgency": {"answer": "critical", "confidence": 0.6651, "probabilities": {"low": 0.0051, "medium": 0.0156, "high": 0.3142, "critical": 0.6651}}}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Status: research prototype, and the docs are blunt about it. The Jev-agreement numbers use LLM-authored questions with no human labels. The one fitted head that ships cannot be reproduced from the repo, because its training seed is withheld. If you have a fixed label set and training data, train a classifier instead. All of it is written down in a LIMITATIONS.md.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The boring lesson
&lt;/h2&gt;

&lt;p&gt;This field is moving embarrassingly fast.&lt;br&gt;
But the lesson I'd actually keep from these weeks is a boring one: &lt;strong&gt;classical ML never left.&lt;/strong&gt; Every number in the table above says so. Wherever a fixed label set and training data exist, something trained the old way wins. Jev's 95.5% on BANKING77 against my 87.5%, the fine-tuned 2B sitting 16 points above my zero-training readout on typed-decisions. &lt;strong&gt;Reading logits doesn't replace training&lt;/strong&gt;. It covers the corner cases training can't reach e.g. questions you can't enumerate ahead of time, no labels to collect, data that isn't allowed to leave the machine etc.&lt;/p&gt;

&lt;p&gt;So before throwing this at a task, the checklist is the one classical ML wrote decades ago. Fixed labels and enough data? Train a classifier, it's still unbeatable there. Data that can't leave the machine, or questions that change per request? That's where reading the model's own probabilities earns its keep. In between, renting a hosted API is probably a fine deal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Oh right, the question
&lt;/h2&gt;

&lt;p&gt;Ah, I actually never answered the "how does a model &lt;em&gt;decide&lt;/em&gt; instead of write" part from the beginning. Short version, and experienced ML people will roll their eyes at how obvious it is: the decision seems to form well before the end of the network. My tiny head reads block 16 of 32 and still answers the "what next" question at 0.826 accuracy. AnyJev's own measurements found a middle block to be a better feature space than the final one, since the last blocks are busy converting the answer into token space. And NeoJev keeps 56 of 64 layers, which suggests there is slack even higher up. So roughly: the deciding happens in the first half to two thirds of the layers, and the rest is more like the writing department.&lt;/p&gt;

&lt;p&gt;Am I even right here? Probably, for these models, by my measurements. That's as far as I'd push it.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>jev</category>
      <category>decisionmodels</category>
      <category>mlx</category>
    </item>
    <item>
      <title>Home Manager: never set up a laptop twice</title>
      <dc:creator>Alexander Shagov</dc:creator>
      <pubDate>Wed, 29 Jul 2026 14:54:55 +0000</pubDate>
      <link>https://dev.to/alexander_shagov/home-manager-never-set-up-a-laptop-twice-451p</link>
      <guid>https://dev.to/alexander_shagov/home-manager-never-set-up-a-laptop-twice-451p</guid>
      <description>&lt;p&gt;How I restore my entire Mac user environment from one Git repo with a single command&lt;/p&gt;




&lt;p&gt;I wrote earlier about &lt;a href="https://dev.to/alexander_shagov/why-use-nix-even-on-macos-786"&gt;why I use Nix on macOS&lt;/a&gt;. This is the follow-up: the same idea, applied to the whole machine instead of one project.&lt;/p&gt;

&lt;p&gt;My entire user environment lives in a single Git repo. Packages, shell config, aliases, git, tmux, the editor. Everything I care about, basically. When I get a new Mac, I clone it, run one command, and I'm back.&lt;/p&gt;




&lt;p&gt;The recipe is short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Nix (the &lt;a href="https://determinate.systems/" rel="noopener noreferrer"&gt;Determinate installer&lt;/a&gt;, more on that in a sec).&lt;/li&gt;
&lt;li&gt;Clone the config repo into &lt;code&gt;~/.config/home-manager&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;home-manager switch&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# flakes on by default&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://install.determinate.systems/nix | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;install

&lt;/span&gt;git clone https://github.com/you/your-config ~/.config/home-manager
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.config/home-manager

home-manager switch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line does the real work. It builds the whole environment from &lt;code&gt;flake.nix&lt;/code&gt; and &lt;code&gt;home.nix&lt;/code&gt; and installs it. If I nuke the machine and start over, I just pull the repo, run the command, and end up on the exact same setup, because &lt;code&gt;flake.lock&lt;/code&gt; pins every input to a fixed version.&lt;/p&gt;




&lt;p&gt;Here's the config I actually run. The flake just declares inputs and points at &lt;code&gt;home.nix&lt;/code&gt;:&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;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Home Manager configuration of alexlaptop"&lt;/span&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;"github:nixos/nixpkgs/nixos-unstable"&lt;/span&gt;&lt;span class="p"&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="p"&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:nix-community/home-manager"&lt;/span&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="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;follows&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="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;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;home-manager&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="kd"&gt;let&lt;/span&gt;
      &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"x86_64-darwin"&lt;/span&gt;&lt;span class="p"&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;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;legacyPackages&lt;/span&gt;&lt;span class="o"&gt;.&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="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;homeConfigurations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"alexlaptop"&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;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;homeManagerConfiguration&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;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="sx"&gt;./home.nix&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;p&gt;And &lt;code&gt;home.nix&lt;/code&gt; is where everything I want lives: packages, aliases, shell, tools:&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="kd"&gt;let&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="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="nv"&gt;be&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bundle exec"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;bi&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bundle install"&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;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;"alexlaptop"&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;"/Users/alexlaptop"&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;"26.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;ripgrep&lt;/span&gt; &lt;span class="nv"&gt;fd&lt;/span&gt; &lt;span class="nv"&gt;fzf&lt;/span&gt; &lt;span class="nv"&gt;bat&lt;/span&gt; &lt;span class="nv"&gt;tree&lt;/span&gt; &lt;span class="nv"&gt;jq&lt;/span&gt; &lt;span class="nv"&gt;htop&lt;/span&gt;
    &lt;span class="nv"&gt;direnv&lt;/span&gt; &lt;span class="nv"&gt;tmux&lt;/span&gt;
    &lt;span class="nv"&gt;ffmpeg_6&lt;/span&gt; &lt;span class="nv"&gt;poppler-utils&lt;/span&gt; &lt;span class="nv"&gt;exiftool&lt;/span&gt;
    &lt;span class="nv"&gt;emacs-pgtk&lt;/span&gt;
    &lt;span class="c"&gt;# ai&lt;/span&gt;
    &lt;span class="nv"&gt;claude-code&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="nv"&gt;shellAliases&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="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="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="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;shellAliases&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;initContent&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;      autoload -Uz vcs_info&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      precmd() { vcs_info; }&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      zstyle ':vcs_info:*' enable git&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      zstyle ':vcs_info:*' formats ' (%b)'&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='%~&lt;/span&gt;&lt;span class="se"&gt;''$&lt;/span&gt;&lt;span class="s2"&gt;{vcs_info_msg_0_} %# '&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="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="nv"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fzf&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="nv"&gt;enableZshIntegration&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="nv"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;tmux&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="nv"&gt;extraConfig&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;      unbind C-b&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g prefix C-z&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      bind C-z send-prefix&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing worth saying is that I don't use nix-darwin. Home Manager looks after my &lt;em&gt;user&lt;/em&gt; environment (the tools, the shell, the dotfiles). It doesn't touch macOS itself. Which is fine by me, honestly, because the things that drift between machines and eat my afternoon are exactly the things Home Manager already handles. If I ever want the system layer on top of that, nix-darwin is the obvious next step. Or the full NixOS, on a separate Linux laptop.. I just never felt the need.&lt;/p&gt;




&lt;p&gt;A quick word on the installer, because it matters more than I expected. I use &lt;a href="https://determinate.systems/" rel="noopener noreferrer"&gt;Determinate Systems'&lt;/a&gt; one rather than the stock Nix installer.&lt;/p&gt;

&lt;p&gt;The main reason is flakes. Determinate turns on &lt;code&gt;flakes&lt;/code&gt; and &lt;code&gt;nix-command&lt;/code&gt; out of the box. By 2026 flakes are just how Nix works (every real project uses them), even though upstream still slaps the "experimental" label on them. That label's been sitting there since 2021 and at this point it's mostly inertia.&lt;/p&gt;

&lt;p&gt;The other two are practical. Stock Nix had a nasty habit of breaking after a major macOS update. Determinate runs its own daemon, and I haven't had a dead &lt;code&gt;/nix&lt;/code&gt; since I switched. And if I ever want out, every change is logged to &lt;code&gt;/nix/receipt.json&lt;/code&gt;, so &lt;code&gt;sudo /nix/nix-installer uninstall&lt;/code&gt; reverses the whole configuration cleanly.&lt;/p&gt;




&lt;p&gt;Is it worth it? Well.. I'll be honest here, the Nix language is genuinely weird. The error messages are rough, and option names get renamed between releases, which is annoying. But still it is worth it. &lt;strong&gt;I don't think I'll ever spend an afternoon setting up a laptop again.&lt;/strong&gt; Packages, dotfiles, the whole setup, exactly where I left them, from one repo and one command.&lt;/p&gt;

&lt;p&gt;If you're interested, check the &lt;a href="https://dev.to/alexander_shagov/nix-first-steps-4066"&gt;first steps article&lt;/a&gt;, understand the vocabulary before jumping into the configs, it's easy to get lost even though it boils down to few commands and files eventually.&lt;/p&gt;

&lt;p&gt;You might also visit &lt;a href="https://shahau.com/" rel="noopener noreferrer"&gt;my personal blog&lt;/a&gt; for more topics (not only nix-related).&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

</description>
      <category>nix</category>
      <category>nixos</category>
      <category>devex</category>
    </item>
    <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://dev.to/alexander_shagov/how-llm-users-accidentally-do-tdd-without-realizing-it-3dc</link>
      <guid>https://dev.to/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://dev.to/alexander_shagov/configuring-your-own-deep-research-tool-using-nix-flakes-56ah</link>
      <guid>https://dev.to/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://dev.to/alexander_shagov/ruby-on-rails-your-service-layer-is-a-lie-1nj8</link>
      <guid>https://dev.to/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://dev.to/alexander_shagov/why-use-nix-even-on-macos-786</link>
      <guid>https://dev.to/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://dev.to/alexander_shagov/balancing-authenticity-and-self-promotion-in-the-tech-industry-3jl3</link>
      <guid>https://dev.to/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://dev.to/alexander_shagov/many-architectural-principles-can-be-simplified-to-the-io-to-the-boundary-principle-2hg6</link>
      <guid>https://dev.to/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://dev.to/alexander_shagov/ruby-on-rails-rethinking-service-objects-4l0b</link>
      <guid>https://dev.to/alexander_shagov/ruby-on-rails-rethinking-service-objects-4l0b</guid>
      <description>&lt;p&gt;(!) I've updated the article. &lt;a href="https://shahau.com/blog/rethinking-service-objects-ror/" rel="noopener noreferrer"&gt;You might read the updated version on my personal blog.&lt;/a&gt;&lt;/p&gt;




&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://dev.to/alexander_shagov/nix-first-steps-4066</link>
      <guid>https://dev.to/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>
