<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Modular-Monolith on Pasi Dev</title><link>https://dev.pasi.pl/tags/modular-monolith/</link><description>Recent content in Modular-Monolith on Pasi Dev</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 09 Jun 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://dev.pasi.pl/tags/modular-monolith/index.xml" rel="self" type="application/rss+xml"/><item><title>Misusing Command and Query Buses in Modular Monoliths: A NestJS Perspective</title><link>https://dev.pasi.pl/misusing-command-and-query-buses-in-modular-monoliths-a-nestjs-perspective/</link><pubDate>Mon, 09 Jun 2025 00:00:00 +0000</pubDate><guid>https://dev.pasi.pl/misusing-command-and-query-buses-in-modular-monoliths-a-nestjs-perspective/</guid><description>&lt;p&gt;In recent years, architectural patterns like &lt;strong&gt;Command Bus&lt;/strong&gt; and &lt;strong&gt;Query Bus&lt;/strong&gt; have gained popularity, especially in TypeScript backends using NestJS and its &lt;code&gt;@nestjs/cqrs&lt;/code&gt; package. While these patterns offer separation of concerns, they are often overused in places where a simple, clean application service or facade would be better.&lt;/p&gt;
&lt;h2 id="the-problem-overengineering-in-a-modular-monolith"&gt;The Problem: Overengineering in a Modular Monolith&lt;/h2&gt;
&lt;p&gt;Consider this common anti-pattern in NestJS:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// update-profile.command.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;export&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;class&lt;/span&gt; UpdateProfileCommand {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;constructor&lt;/span&gt;(&lt;span style="color:#ff7b72"&gt;public&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;readonly&lt;/span&gt; userId: &lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;, &lt;span style="color:#ff7b72"&gt;public&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;readonly&lt;/span&gt; nickname: &lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// update-profile.handler.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;@CommandHandler&lt;/span&gt;(UpdateProfileCommand)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;export&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;class&lt;/span&gt; UpdateProfileHandler &lt;span style="color:#ff7b72"&gt;implements&lt;/span&gt; ICommandHandler {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;constructor&lt;/span&gt;(&lt;span style="color:#ff7b72"&gt;private&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;readonly&lt;/span&gt; userRepository: &lt;span style="color:#ff7b72"&gt;UserRepository&lt;/span&gt;) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; execute(command: &lt;span style="color:#ff7b72"&gt;UpdateProfileCommand&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;const&lt;/span&gt; user &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;this&lt;/span&gt;.userRepository.findById(command.userId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user.updateNickname(command.nickname);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;this&lt;/span&gt;.userRepository.save(user);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// get-profile.query.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;export&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;class&lt;/span&gt; GetProfileQuery {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;constructor&lt;/span&gt;(&lt;span style="color:#ff7b72"&gt;public&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;readonly&lt;/span&gt; userId: &lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// get-profile.handler.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;@QueryHandler&lt;/span&gt;(GetProfileQuery)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;export&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;class&lt;/span&gt; GetProfileHandler &lt;span style="color:#ff7b72"&gt;implements&lt;/span&gt; IQueryHandler {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;constructor&lt;/span&gt;(&lt;span style="color:#ff7b72"&gt;private&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;readonly&lt;/span&gt; userRepository: &lt;span style="color:#ff7b72"&gt;UserRepository&lt;/span&gt;) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; execute(query: &lt;span style="color:#ff7b72"&gt;GetProfileQuery&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;const&lt;/span&gt; user &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;this&lt;/span&gt;.userRepository.findById(query.userId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; { userId: &lt;span style="color:#ff7b72"&gt;user.id&lt;/span&gt;, nickname: &lt;span style="color:#ff7b72"&gt;user.nickname&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// user-profile.controller.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;this&lt;/span&gt;.commandBus.execute(&lt;span style="color:#ff7b72"&gt;new&lt;/span&gt; UpdateProfileCommand(userId, nickname));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;this&lt;/span&gt;.queryBus.execute(&lt;span style="color:#ff7b72"&gt;new&lt;/span&gt; GetProfileQuery(userId));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s &lt;strong&gt;four classes&lt;/strong&gt; and &lt;strong&gt;four files&lt;/strong&gt; just to write 2 methods. Where is the value in that? Where is the gain?&lt;/p&gt;</description></item></channel></rss>