paint-brush
The Mediator Pattern for Ultimate Coding Speed by@josejaviasilis
2,477 reads
2,477 reads

The Mediator Pattern for Ultimate Coding Speed

by Jose Javi AsilisSeptember 23rd, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Help structure your code by using a simple CQRS (Or CQS) pattern that will allow you to write near infinite function definitions without breaking your code. This is strongly my opinion and I’m more than open to hearing from you! Hopefully, this will help you in your next project. Can run in Node, Bun, Deno, and the browser.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - The Mediator Pattern for Ultimate Coding Speed
Jose Javi Asilis HackerNoon profile picture

A simple CQRS (Or CQS for the matter) pattern that will allow you to write near infinite function definitions without breaking your code

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.

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).


The traditional 3-layered application. After your application grows to a certain extent, this doesn't work anymore


This lead me to research and stumble upon Andrea Saltarello’s and Dino Esposito’s mind-blowing Architecting Application for Enterprise 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.


From the 3-layered 2-tier approach, my applications began looking something like this:


While this may vary from individual to individual, the onion architecture basically says that every layer is only allowed to interact with the one that is next to it, e.g: The Application Service shouldn't interact with the Domain Model, it should only interact with the Domain Service. Both the application and the domain service can be broken up into Commands and Queries, which is the purpose of the post.


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.


Comand-Query Responsibility Segregation 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.


I think it was Greg Young 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.


Fortunately for us, nowadays, with the advent of serverless and powerful services such as DynamoDB, Planet Scale, Supabase, SurrealDB, and many more, those physical DB separations that Greg suggested may not be needed as these utilities have all the optimizations built in while making it transparent to the developer.


Benefits and the power of CQRS

As mentioned above, the CQRS pattern allows you to break your service or application layers into simple, reduced commands or queries that will allow you to write very specific code.


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 won’t need to touch the existing code, as you end up creating new files instead.
  • Refactoring can be performed in parts, because both commands and queries are modularized. This becomes even more powerful once you realize that you can just add a “V2” command and slowly strangle the other portions of the app! Yes, you can even have a V1 and a V2 running simultaneously! Hence, making transitioning your code easier.
  • 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 a very simplified version of CQRS that I’ve been using in production and hopefully will allow you to move faster 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!)


You can use this as your starting ground, and add more complexity as your project needs it.


This example is written in TypeScript, and there’s a GitHub repo that was built using Deno, 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.


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 observable. You define a simple object that will hold other objects in a key-value fashion:


// File: mediator.ts
const commands = {}


The commands object will hold a string key (typethat 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).


You then populate them by using a register function:


// File: mediator.ts
export function registerCommandOrQuery(type, arg) {
 commands[type] = arg
}


In which type is a string that will hold the command or the query name, and arg will be the reference to the function that you’d like to execute.


Next, you call the registerCommandOrQuery for each of the functions to use:


// File: register.ts
registerCommandOrQuery({
  type: "REGISTER_USER",
  arg: RegisterUserCommandHandler,
});
registerCommandOrQuery({
  type: "GET_USERS",
  arg: GetUsersQueryHandler,
});


Check the repo to understand how the pieces fit together.


This will add the RegisterUserCommandHandle under the ”REGISTER_USER” key and the GetUsersQueryHandle under the ”GET_USERS” key.


Finally, you’ll be able to fire them or dispatch them with a dispatch function:


// File: dispatch.ts
export async function dispatch({ type, arg }) {
 return commands[type](arg);
}


In which type is a string that will hold the command or the query name, and arg will be the arguments for the function to be passed (for dispatch).


Note that type is the same for both the registerCommandOrQuery and dispatch


Call the dispatch method practically anywhere in your application:


dispatch({
  type: "REGISTER_USER",
  arg: {
     userId: "my userId",
     password: "my password"
  }
});


Where REGISTER_USER was the one that I used to define RegisterUserCommandHandler. If I were to have a V2.


That’s it!

It’s as simple as that. No magic and no sorcery. The command object holds a reference to our command and queries, and we use the dispatch function to launch them. This simple addition will make your applications more portable.


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 MediatorCommandsAndQueries type, which will be the typing glue of our command and 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 dispatch function typing superpowers. Once you start writing the proper type the arg you will be greeted with autocomplete and type-checking functionalities!

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 registerUserCommand.ts and will define both its type, and the function, which will be the ones getting registered in the MediatorCommandsAndQueries type:


// 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 Jovo without modifying most of our code, because they won’t be tied to the framework or the infrastructure.


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:


  1. This pattern is quite basic and there will possibly be some limitations.
  2. 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 - ctrl + shift + p (Win/Lin) or cmd + shift + p (Mac)
  3. The mediator pattern can also be used to implement middleware, such as logging and tracing. See the GitHub repo to see its power.
  4. 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: [email protected]

IG (English): https://instagram.com/javiasilis

TikTok: https://tiktok.com/@javiasilis

Twitter: https://twitter.com/javiasilis

LinkedIn: http://linkedin.com/in/javiasilis