Boosting Node.js Performance with TypeDI and the Strategy Pattern

What is the Strategy Pattern?

The strategy pattern is a software design pattern that allows you to group related implementations together and interact with them using a central class called the Context. This pattern is particularly useful when you need to support multiple payment or messaging providers within a single application.

Dependency Injection and Loose Coupling

Dependency injection is a software design pattern that enables methods to receive dependencies as parameters. This technique helps prevent tightly coupled software design and makes it easier to maintain your codebase over time.

Why Use TypeDI?

TypeDI is a dependency injection container that helps you manage dependencies in your Node.js application. It uses the experimental TypeScript Reflection API to provide an Inversion of Control (IoC) container that makes it easy to inject values into class properties via their constructors.

Creating a Proof of Concept API

To demonstrate the power of TypeDI and the strategy pattern, we’ll create a simple API that stores and retrieves transaction records using Redis and Memcached as caching providers.

Setup and Installation

To get started, you’ll need to install the following dependencies:

  • node-redis
  • memcached
  • typedi

Next, create a new TypeScript project and edit the tsconfig.json file to allow decorators and emitting decorator metadata.

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Configuring the Express.js Application

Create a config singleton object to export values used in other modules. Establish persistent TCP connections to your Redis and Memcached services in the application startup and expose these connections to the rest of your application.

import * as Redis from 'ioredis';
import * as Memcached from 'emcached';

const config = {
  redis: new Redis(),
  memcached: new Memcached()
};

export default config;

Bootstrapping the Application

Bootstrap your Express application in src/index.ts. Import reflect-metadata and use TypeDI to provide an IoC container.

import 'eflect-metadata';
import { Container } from 'typedi';

const container = new Container();

//...

container.set('config', config);

app.listen(3000, () => {
  console.log('Server listening on port 3000');
});

Building the Cache Provider

Define the cache provider using the strategy pattern. Create a Context class that identifies all available strategies and exposes methods for consumers to switch between them.

interface CacheStrategy {
  get(key: string): Promise<string>;
  set(key: string, value: string): Promise<void>;
}

class Context {
  private strategy: CacheStrategy;

  constructor(strategy: CacheStrategy) {
    this.strategy = strategy;
  }

  async get(key: string) {
    return this.strategy.get(key);
  }

  async set(key: string, value: string) {
    return this.strategy.set(key, value);
  }
}

class RedisCacheStrategy implements CacheStrategy {
  //...
}

class MemcachedCacheStrategy implements CacheStrategy {
  //...
}

Setting up the Transactions Module

Expose API endpoints to save and retrieve transactions. Use TypeDI to dynamically inject the TransactionService inside the TransactionController.

@Service()
class TransactionService {
  constructor(@Inject('config') private config: Config) { }

  async saveTransaction(transaction: Transaction) {
    //...
  }

  async getTransaction(id: string) {
    //...
  }
}

@Controller('/transactions')
class TransactionController {
  constructor(private transactionService: TransactionService) { }

  @Post('/')
  async saveTransaction(@Body() transaction: Transaction) {
    return this.transactionService.saveTransaction(transaction);
  }

  @Get('/:id')
  async getTransaction(@Param('id') id: string) {
    return this.transactionService.getTransaction(id);
  }
}

Manually Testing the Application

Use curl to test the application manually. Add transactions and retrieve them to verify that the caching providers are working correctly.

$ curl -X POST -H "Content-Type: application/json" -d '{"amount": 100}' http://localhost:3000/transactions
$ curl http://localhost:3000/transactions/1

Improving Performance with Distributed Caches

Distributed caches with fallbacks can significantly improve the performance of cloud applications. By using TypeDI and the strategy pattern, you can create a robust caching system that meets your application’s needs.

Leave a Reply