Please, note that this is strongly my opinion and I’m more than open to hearing from you! Hopefully, this will help you in your next project 😉. 👨💻TL;DR GitHub code Repo: (Implemented in Deno & TypeScript) - Can run in Node, Bun, and the browser. https://github.com/superjose/simple-cqrs?embedable=true Head to the “Simple CQRS” section for the actual implementation. The meaty 🍖 stuff I've been working on startups ever since 2015. One of the capabilities of a true and tried startup is the ability to move fast and adapt to changes. It was late 2015, and my application started to suffer 😥. Having limited knowledge about software architecture, I thought that the traditional view-business logic partnered with dependency injection will cut it (I was building with ASP.NET MVC 4.5 at that time). Unfortunately, that was not the case. After a while, classes, and methods became entangled with each other. It became painful adding and or extending current functionality even though I had no concrete dependencies in most of my files. This meant that my time to market was greatly delayed, and bug fixing could introduce regressions (as I didn’t know why tests were important back then). This lead me to research and stumble upon Andrea Saltarello’s and Dino Esposito’s mind-blowing book. I learned how to structure complex applications using lesser-known approaches: Domain Driven Design, sagas, event buses, event sourcing, clean architecture, onion architecture, CQRS, and many other patterns. While I didn’t grasp them at first, the more I matured with writing code and working on other startups, I began to realize their importance, and the way I’d do applications today would be wildly different from how I did them 7 years back. Architecting Application for Enterprise From the 3-layered 2-tier approach, my applications began looking something like this: This greatly reduced the spaghetti code that surfaced. Breaking functionality into bounded contexts and then serving them through different layers, I was able to define clear boundaries, introduce new features and replace parts of the application with limited damage. Yet, there were instances in which some of these layers presented a series of challenges: They became too big to modify or introduce new functionality. There were times I wanted to introduce a V2 of a method but I didn’t want to affect the V1 of the application, and duplicating the file was cumbersome as there were other methods relying on it. That’s when I re-read the architecting book and found some videos about clean architecture. It re-introduced me to CQRS through the . MediatR library from Jimmy Bogard allows you to break your service/application layers into commands and queries, giving you the possibility to program a single responsibility per action. You’d then communicate across the layers through a single dependency, a dispatch, which would fire a command or a query and return the appropriate result (For purists, commands do not return, but I found it a little bit impractical). This is similar to Redux for those who’ve worked in SPAs and GraphQL mutations and queries. Comand-Query Responsibility Segregation I think it was who brought it up for the first time as a successor to the Command Query Separation (CQS) pattern. They are very similar except that CQRS splits the data persistence layer and the DB underneath in two. This would allow you to have one DB for reads (queries), and one for writes (commands). (You’d hydrate/insert data into the query DB through eventual consistency or another syncing mechanism). That alone can become pretty complex, and that is out of the scope of this post. Greg Young Fortunately for us, nowadays, with the advent of serverless and powerful services such as DynamoDB, Planet Scale, Supabase, , and many more, those physical DB separations that Greg suggested be needed as these utilities have all the optimizations built in while making it transparent to the developer. SurrealDB may not Benefits and the power of CQRS As mentioned above, the CQRS pattern allows you to into that will allow you to write very specific code. break your service or application layers simple, reduced commands or queries This brings the following benefits: You won’t have (necessarily) a file that can span 1,000s of lines of code trying to do several things at once. To extend the functionality you as you end up creating new files instead. won’t need to touch the existing code, Refactoring can , because both commands and queries . This becomes even more powerful once you realize that you can just add a “V2” command and slowly the other portions of the app! Hence, making transitioning your code easier. be performed in parts are modularized strangle Yes, you can even have a V1 and a V2 running ! simultaneously The code is localized. You can even add simple logging that can monitor the entire traces of commands in a single place! It allows you to better separate your code from your infrastructure. Just add a dispatcher inside a Lambda resolver (for example) and you can work as usual. It doesn’t need to be complicated for incredible returns You’ll see down below that I’ll be sharing that I’ve been using in production and hopefully will in your coding projects (especially in startup scenarios). This is agnostic to any framework or library you use (If your framework has built-in CQRS functionality, like NestJS, I strongly encourage you to check it out!) a very simplified version of CQRS allow you to move faster You can use this as your starting ground, and add more complexity as your project needs it. This example is written in TypeScript, and that was built using , but works really well in Node, Bun, the browser, Cloudflare Workers, Edge Workers, Lambda, Azure Functions, or whatever. It’s just plain JavaScript and there’s no magic. there’s a GitHub repo Deno In addition, this can be extrapolated to other languages like Ruby, Python, C#, Java, Rust, and many more. You can download it here: https://github.com/superjose/simple-cqrs Simple CQRS The Mediator Pattern As I said previously, if you’ve used Redux or GraphQL, you are already implementing this pattern without you knowing! We will implement our own version of it so we can configure our app to support commands and queries (akin to mutations and queries in GraphQL). The mediator by itself is a form of an . You define a simple object that will hold other objects in a key-value fashion: observable // File: mediator.ts const commands = {} The object will hold a string key ( that will refer to the command’s or query’s name) and then a reference to a function, which will be the function to execute (whether it’s a command or a query). commands type You then populate them by using a register function: // File: mediator.ts export function registerCommandOrQuery(type, arg) { commands[type] = arg } In which is a string that will hold the command or the query name, and will be the reference to the function that you’d like to execute. type arg Next, you call the for each of the functions to use: registerCommandOrQuery // File: register.ts registerCommandOrQuery({ type: "REGISTER_USER", arg: RegisterUserCommandHandler, }); registerCommandOrQuery({ type: "GET_USERS", arg: GetUsersQueryHandler, }); Check the to understand how the pieces fit together. repo This will add the under the key and the under the key. RegisterUserCommandHandle ”REGISTER_USER” GetUsersQueryHandle ”GET_USERS” Finally, you’ll be able to fire them or dispatch them with a function: dispatch // File: dispatch.ts export async function dispatch({ type, arg }) { return commands[type](arg); } In which is a string that will hold the command or the query name, and will be the arguments for the function to be passed (for ). type arg dispatch Note that is the same for both the and type registerCommandOrQuery dispatch Call the dispatch method in your application: practically anywhere dispatch({ type: "REGISTER_USER", arg: { userId: "my userId", password: "my password" } }); Where was the one that I used to define . If I were to have a V2. REGISTER_USER RegisterUserCommandHandler That’s it! It’s as simple as that. No magic and no sorcery. The object holds a reference to our command and queries, and we use the function to launch them. This simple addition will make your applications more portable. command dispatch But wait! We’ll break it down further and spice it up with TypeScript! TypeScript With TypeScript, there’s additional work to do, as we need to provide type safety to each of the elements: // File: mediator.ts // Same code as previous section, but putting it all together + typings import { MediatorCommandsAndQueries } from "./mediator-commands.ts"; const commands: { [x: string]: MediatorCommandsAndQueries["arg"]; } = {}; export function registerCommandOrQuery({ type, arg, }: MediatorCommandsAndQueries) { commands[type] = arg; } export async function dispatch<TFunction extends (...args: any) => any>({ type, arg, }: MediatorCommandsAndQueries) { return (commands[type] as Function)(arg) as ReturnType<TFunction>; } In TypeScript, we have another file that holds our type, which will be the typing glue of our and : MediatorCommandsAndQueries command dispatch // File: mediator-commands.ts import { CreateNewPostCommand, CreateNewPostCommandHandler, GetAllPostsQuery, GetAllPostsQueryHandler, GetUsersQuery, GetUsersQueryHandler, RegisterUserCommand, RegisterUserCommandHandler, } from "../bounded_contexts/index.ts"; export type MediatorCommandsAndQueries = | { type: "REGISTER_USER"; arg: RegisterUserCommand | typeof RegisterUserCommandHandler; } | { type: "GET_USERS"; arg: GetUsersQuery | typeof GetUsersQueryHandler; } | { type: "GET_POSTS"; arg: GetAllPostsQuery | typeof GetAllPostsQueryHandler; } | { type: "CREATE_POST"; arg: CreateNewPostCommand | typeof CreateNewPostCommandHandler; }; You’d find all those function definitions in the . GitHub repo I’d love to get some feedback on this approach. This was the best I could do that could be shared between registering the commands and queries and dispatching them with the same type. This will give the function typing superpowers. Once you start writing the proper the you will be greeted with autocomplete and type-checking functionalities! dispatch type arg Deep Dive in Queries and Commands Queries and Commands are just POJO (Plain Old JavaScript Objects) simple files. At the bare minimum, it contains a single function that is exported! No external dependency needed, which adds the huge benefit of having your application decoupled from a specific infrastructure or technology. For code cleanness, I do add the function’s parameter as a separate type, so there are 2 . In this case, we will call this file and will define both its type, and the function, which will be the ones getting registered in the type: registerUserCommand.ts MediatorCommandsAndQueries // File: registerUserCommand.ts export type RegisterUserCommand = { username: string; password: string; }; export function RegisterUserCommandHandler(cmd: RegisterUserCommand) { console.log("Apply the registration user command handler", cmd); } We’d then register this command in a register.ts file (like we had above): // File: register.ts registerCommandOrQuery({ type: "REGISTER_USER", arg: RegisterUserCommandHandler, }); And then call it in our app: await dispatch({ type: "GET_POSTS", arg: { userId: "get user Id from token", }, }); Using it in Express, Oak or other libraries Once you have your functions defined and the dispatcher ready, you can see magic happen by including them where you’d usually have some application logic. This could be in some express router handlers, or GraphQL resolver. Here’s an example using Oak, an HTTP library built for Deno.js: const router = new Router() .get("/posts", async (ctx) => { await dispatch({ type: "GET_POSTS", arg: { userId: "get user Id from token", }, }); }) .post("/posts", async (ctx) => { await dispatch({ type: "CREATE_POST", arg: { post: { content: "the content", title: "the title", }, userId: "The user Id", }, }); }) .get("/users", async (ctx) => { await dispatch({ type: "GET_USERS", arg: { sort: "ascending", }, }); }) .post("/users", async (ctx) => { await dispatch({ type: "REGISTER_USER", arg: { password: "pass", username: "user", }, }); }); You’d see that all of your routes now have become exceptionally thin. In addition, you only have one single dependency in your route folder, clearing out the noise. In the future, if you decide to swap your REST endpoints or the majority of them for a GraphQL server like Apollo or maybe implement a gRPC you can do them in an instant. Heck, we can go as far as to implement a voice assistant such as the Echo or Google Home using without modifying most of our code, because they won’t be tied to the framework or the infrastructure. Jovo With this, I think we have achieved a decent level of separation of concerns between our layers! Some Final notes You can use this as your starting ground, and add more complexity as your project needed it. Please note that: This pattern is quite basic and there will possibly be some limitations. There may be the counterargument that this will increase considerably the number of files of the project. This is true, to solve this you can leverage the quick file opening from VS Code or your file editor (like the one in VS Code - (Win/Lin) or (Mac) ctrl + shift + p cmd + shift + p The mediator pattern can also be used to implement middleware, such as logging and tracing. See the to see its power. GitHub repo This pattern can be extended to hold multiple commands and create an event dispatcher, making your application raise domain events and then publish them. Thank you very much for taking the time to read this post 😊! Let me know if there are any comments or improvements to this approach 🙏! I love to keep learning new stuff. Hopefully, this will be of great help to structure future projects! Follow me on social media! Check me out on my other social media channels to discuss startups and tech in general. Reach out if you want to discuss the post. Site: javiasilis.com Email: jose@javiasilis.com IG (English): https://instagram.com/javiasilis TikTok: https://tiktok.com/@javiasilis Twitter: https://twitter.com/javiasilis LinkedIn: http://linkedin.com/in/javiasilis