Hexagonal Architecture Abuse: When Organization Becomes Obfuscation

Hexagonal architecture (also known as Ports and Adapters) is a powerful way to design software that is independent of frameworks, databases, or UI layers. Its main goal is to isolate the core business logic from the outside world by enforcing clear boundaries. However, in many projects, this pattern is over-engineered to the point where the supposed simplicity turns into a maze of indirection, deeply nested folders, and needless abstractions. The Problem: Overstructuring a Simple Concept Many developers (including me) start with the intent of using hexagonal architecture but quickly spiral into the following: ...

June 11, 2025 · 5 min

Misusing Command and Query Buses in Modular Monoliths: A NestJS Perspective

In recent years, architectural patterns like Command Bus and Query Bus have gained popularity, especially in TypeScript backends using NestJS and its @nestjs/cqrs 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. The Problem: Overengineering in a Modular Monolith Consider this common anti-pattern in NestJS: // update-profile.command.ts export class UpdateProfileCommand { constructor(public readonly userId: string, public readonly nickname: string) {} } // update-profile.handler.ts @CommandHandler(UpdateProfileCommand) export class UpdateProfileHandler implements ICommandHandler { constructor(private readonly userRepository: UserRepository) {} async execute(command: UpdateProfileCommand) { const user = await this.userRepository.findById(command.userId); user.updateNickname(command.nickname); await this.userRepository.save(user); } } // get-profile.query.ts export class GetProfileQuery { constructor(public readonly userId: string) {} } // get-profile.handler.ts @QueryHandler(GetProfileQuery) export class GetProfileHandler implements IQueryHandler { constructor(private readonly userRepository: UserRepository) {} async execute(query: GetProfileQuery) { const user = await this.userRepository.findById(query.userId); return { userId: user.id, nickname: user.nickname }; } } // user-profile.controller.ts await this.commandBus.execute(new UpdateProfileCommand(userId, nickname)); await this.queryBus.execute(new GetProfileQuery(userId)); That’s four classes and four files just to write 2 methods. Where is the value in that? Where is the gain? ...

June 9, 2025 · 7 min